nufatfs
Version:
A new async-friendly library for accessing FAT16 and FAT32 filesystems
215 lines (214 loc) • 9.55 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FatFSFileHandle = exports.FatFilesystem = void 0;
const low_level_1 = require("./low-level");
const types_1 = require("./types");
const utils_1 = require("./utils");
const constructors_1 = require("./constructors");
// This class aims to contain the demons stored in LowLevelFatFilesystem.
class FatFilesystem {
constructor(fat) {
this.fat = fat;
}
static async create(driver, bypassCoherencyCheck = false, forceFSType) {
const fat = await low_level_1.LowLevelFatFilesystem._create(driver, bypassCoherencyCheck, forceFSType);
return new FatFilesystem(fat);
}
async open(path, writable = false) {
if (writable && !this.fat.isWritable) {
throw new low_level_1.FatError("Cannot open a file for writing on a read-only volume!");
}
const tree = await this.fat.traverseEntries(path);
if (!tree)
return null;
const [parent, entry] = tree.slice(-2);
if (!parent || !entry || entry instanceof low_level_1.CachedDirectory)
return null;
const chain = this.fat.constructClusterChain(entry._firstCluster, true, entry.fileSize);
return new FatFSFileHandle(this.fat, chain, writable, entry, parent);
}
async create(path) {
// Make sure the file we're about to create doesn't already exist.
const existingEntry = await this.fat.traverse(path);
if (existingEntry) {
return null;
}
let lastSlash = path.lastIndexOf("/");
let parent;
if (lastSlash === -1) {
parent = this.fat.root;
}
else {
const parentPath = path.slice(0, lastSlash);
parent = await this.fat.traverse(parentPath);
if (!parent || !(parent instanceof low_level_1.CachedDirectory)) {
return null;
}
}
const name = path.slice(lastSlash + 1);
const encName = (0, utils_1.nameNormalTo83)(name);
const entry = (0, constructors_1.newFatFSDirectoryEntry)(encName, types_1.FatFSDirectoryEntryAttributes.None, 0, 0);
await parent.getEntries(); // Load entry from driver
parent.rawDirectoryEntries.push(entry);
this.fat.markAsAltered(parent);
return new FatFSFileHandle(this.fat, this.fat.constructClusterChain(0, true), true, entry, parent);
}
async listDir(path) {
const entry = await this.fat.traverse(path);
if (!entry || !(entry instanceof low_level_1.CachedDirectory))
return null;
return entry.listDir();
}
async getSizeOf(path) {
const entry = await this.fat.traverse(path);
if (!entry || (entry instanceof low_level_1.CachedDirectory))
return null;
return entry.fileSize;
}
getStats() {
const totalClusters = this.fat.allocator.freemap.length;
const freeClusters = this.fat.allocator.freemap.filter(e => e).length;
return {
totalClusters,
freeClusters,
totalBytes: totalClusters * this.fat.clusterSizeInBytes,
freeBytes: freeClusters * this.fat.clusterSizeInBytes,
};
}
async delete(path) {
const tree = await this.fat.traverseEntries(path);
if (!tree)
return null;
const [parent, entry] = tree.slice(-2);
if (entry instanceof low_level_1.CachedDirectory) {
if ((await entry.getEntries()).length > 2) { // 2 entries - '.' and '..'
throw new low_level_1.FatError("Cannot delete a non-empty directory.");
}
}
if (!entry || !parent || !(parent instanceof low_level_1.CachedDirectory)) {
throw new low_level_1.FatError("File not found!");
}
let rawEntry = entry instanceof low_level_1.CachedDirectory ? entry.underlying : entry;
// Mark as deleted
rawEntry.filename[0] = low_level_1.FAT_MARKER_DELETED;
rawEntry._filenameStr = '';
// Ask the main driver to update this entry's parent
this.fat.markAsAltered(parent);
// Remove the file from parent's cache
parent.rawDirectoryEntries.splice(parent.rawDirectoryEntries.indexOf(entry), 1);
// Construct a chain, then free it
const cluster = rawEntry._firstCluster;
const chain = this.fat.getClusterChainFromFAT(cluster);
this.fat.allocator.addClusterListToFreelist(chain);
// Zero out the chain in FAT
for (let e of chain) {
this.fat.writeFATClusterEntry(e, 0);
}
}
async rename(path, newPath) {
const existingNewTree = await this.fat.traverseEntries(newPath);
if (existingNewTree) {
throw new low_level_1.FatError("File already exists!");
}
let lastSlash = newPath.lastIndexOf("/");
const newParentPath = newPath.includes("/") ? newPath.slice(0, lastSlash) : null;
const newName = newPath.slice(lastSlash + 1);
const newParent = newParentPath ? await this.fat.traverse(newParentPath) : this.fat.root;
const entry = await this.fat.traverse(path);
lastSlash = newPath.lastIndexOf("/");
let oldParentPath = path.slice(0, lastSlash);
const oldParent = await this.fat.traverse(oldParentPath);
if (!(newParent instanceof low_level_1.CachedDirectory) || !(newParent instanceof low_level_1.CachedDirectory)) {
throw new low_level_1.FatError("Cannot move an entry into a file, not a directory!");
}
if (!entry)
throw new low_level_1.FatError("File doens't exist!");
let rawEntry = entry instanceof low_level_1.CachedDirectory ? entry.underlying : entry;
// Update name
rawEntry._filenameStr = (0, utils_1.nameNormalTo83)(newName);
// Renaming will strip LFNs, since we can't encode them yet.
// Cache
await oldParent.getEntries();
let thisEntryIndex = oldParent.rawDirectoryEntries.indexOf(entry);
// Remove both the actual entry reference, and the LFNs that point to it
oldParent.rawDirectoryEntries.splice(thisEntryIndex - rawEntry._lfns, 1 + rawEntry._lfns);
rawEntry._lfns = 0;
// Mark oldParent as in need of a flush
this.fat.markAsAltered(oldParent);
// Cache
await newParent.getEntries();
newParent.rawDirectoryEntries.push(entry);
// Mark newParent as in need of a flush
this.fat.markAsAltered(newParent);
}
async mkdir(path) {
let lastSlash = path.lastIndexOf("/");
const parentPath = path.includes("/") ? path.slice(0, lastSlash) : null;
const name = (0, utils_1.nameNormalTo83)(path.slice(lastSlash + 1));
const parent = parentPath ? await this.fat.traverse(parentPath) : this.fat.root;
if (await parent.findEntry(name))
return;
const rootCluster = this.fat.allocator.allocate(null, 1)[0];
if (!rootCluster || (this.fat.isFat16Or12 && rootCluster.index > 0xFFFF)) {
throw new Error("Cannot allocate next cluster!");
}
// Create the directory entry
const dirEntry = (0, constructors_1.newFatFSDirectoryEntry)(name, types_1.FatFSDirectoryEntryAttributes.Directory, rootCluster.index, 0);
// Create the underlying low-level structures.
const ownDirEntry = (0, constructors_1.newFatFSDirectoryEntry)(". ", types_1.FatFSDirectoryEntryAttributes.Directory, rootCluster.index, 0);
const ownParentEntry = (0, constructors_1.newFatFSDirectoryEntry)(".. ", types_1.FatFSDirectoryEntryAttributes.Directory, parent.initialCluster === -1 ? 0 : parent.initialCluster, 0);
// 'format' the cluster
await rootCluster.write(new Uint8Array(rootCluster.length).fill(0));
// Create the cached entry
const cachedEntry = low_level_1.CachedDirectory.readyMade(this.fat, [ownDirEntry, ownParentEntry], rootCluster.index, dirEntry);
// Cache
await parent.getEntries();
parent.rawDirectoryEntries.push(cachedEntry);
this.fat.markAsAltered(parent);
this.fat.markAsAltered(cachedEntry);
}
async flushMetadataChanges() {
return this.fat.flush();
}
getUnderlying() {
return this.fat;
}
}
exports.FatFilesystem = FatFilesystem;
class FatFSFileHandle {
get length() {
return this.chain.getTotalLength();
}
constructor(fat, chain, writable, underlying, parent) {
this.fat = fat;
this.chain = chain;
this.writable = writable;
this.underlying = underlying;
this.parent = parent;
}
async seek(to) {
await this.chain.seek(to);
}
async read(bytes) {
return this.chain.read(bytes);
}
async readAll() {
return this.chain.readAll();
}
async close() {
if (!this.writable)
return;
await this.chain.flushChanges();
this.underlying.fileSize = this.chain.getTotalLength();
if (this.underlying._firstCluster === 0 && this.underlying.fileSize) {
// This was an empty file before, but is not anymore
const rootCluster = this.chain.links[0].index;
this.underlying._firstCluster = rootCluster;
}
this.fat.markAsAltered(this.parent);
}
async write(data) {
return this.chain.write(data);
}
}
exports.FatFSFileHandle = FatFSFileHandle;