diff --git a/src/backends/memory.ts b/src/backends/memory.ts index b123d6c0..adb6b8e6 100644 --- a/src/backends/memory.ts +++ b/src/backends/memory.ts @@ -1,4 +1,3 @@ -import type { Ino } from '../inode.js'; import type { Backend } from './backend.js'; import { StoreFS } from './store/fs.js'; import { SimpleTransaction, type SimpleSyncStore } from './store/simple.js'; @@ -6,7 +5,7 @@ import { SimpleTransaction, type SimpleSyncStore } from './store/simple.js'; /** * A simple in-memory store */ -export class InMemoryStore extends Map implements SimpleSyncStore { +export class InMemoryStore extends Map implements SimpleSyncStore { public constructor(public name: string = 'tmp') { super(); } diff --git a/src/backends/store/fs.ts b/src/backends/store/fs.ts index 9cb9698c..8f003f34 100644 --- a/src/backends/store/fs.ts +++ b/src/backends/store/fs.ts @@ -5,10 +5,11 @@ import { Errno, ErrnoError } from '../../error.js'; import type { File } from '../../file.js'; import { PreloadFile } from '../../file.js'; import { FileSystem, type FileSystemMetadata } from '../../filesystem.js'; -import { type Ino, Inode, randomIno, rootIno } from '../../inode.js'; +import { Inode, rootIno } from '../../inode.js'; import type { FileType, Stats } from '../../stats.js'; -import { decodeDirListing, encodeDirListing, encodeUTF8 } from '../../utils.js'; +import { decodeDirListing, encodeDirListing, encodeUTF8, randomBigInt } from '../../utils.js'; import type { Store, Transaction } from './store.js'; +import { serialize } from 'utilium'; const maxInodeAllocTries = 5; @@ -70,13 +71,13 @@ export class StoreFS extends FileSystem { const _old = parse(oldPath), _new = parse(newPath), // Remove oldPath from parent's directory listing. - oldDirNode = await this.findINode(tx, _old.dir, 'rename'), - oldDirList = await this.getDirListing(tx, oldDirNode, _old.dir); + oldDirNode = await this.findInode(tx, _old.dir, 'rename'), + oldDirList = decodeDirListing(await this.get(tx, oldDirNode.data, _old.dir, 'rename')); if (!oldDirList[_old.base]) { throw ErrnoError.With('ENOENT', oldPath, 'rename'); } - const nodeId: Ino = oldDirList[_old.base]; + const ino: bigint = oldDirList[_old.base]; delete oldDirList[_old.base]; /* @@ -93,8 +94,8 @@ export class StoreFS extends FileSystem { const sameParent = _new.dir == _old.dir; // Prevent us from re-grabbing the same directory listing, which still contains `old_path.base.` - const newDirNode: Inode = sameParent ? oldDirNode : await this.findINode(tx, _new.dir, 'rename'); - const newDirList: typeof oldDirList = sameParent ? oldDirList : await this.getDirListing(tx, newDirNode, _new.dir); + const newDirNode: Inode = sameParent ? oldDirNode : await this.findInode(tx, _new.dir, 'rename'); + const newDirList: typeof oldDirList = sameParent ? oldDirList : decodeDirListing(await this.get(tx, newDirNode.data, _new.dir, 'rename')); if (newDirList[_new.base]) { // If it's a file, delete it, if it's a directory, throw a permissions error. @@ -102,13 +103,13 @@ export class StoreFS extends FileSystem { if (!existing.toStats().isFile()) { throw ErrnoError.With('EPERM', newPath, 'rename'); } - await tx.remove(existing.ino); + await tx.remove(existing.data); await tx.remove(newDirList[_new.base]); } - newDirList[_new.base] = nodeId; + newDirList[_new.base] = ino; // Commit the two changed directory listings. - await tx.set(oldDirNode.ino, encodeDirListing(oldDirList)); - await tx.set(newDirNode.ino, encodeDirListing(newDirList)); + await tx.set(oldDirNode.data, encodeDirListing(oldDirList)); + await tx.set(newDirNode.data, encodeDirListing(newDirList)); await tx.commit(); } @@ -117,13 +118,13 @@ export class StoreFS extends FileSystem { const _old = parse(oldPath), _new = parse(newPath), // Remove oldPath from parent's directory listing. - oldDirNode = this.findINodeSync(tx, _old.dir, 'rename'), - oldDirList = this.getDirListingSync(tx, oldDirNode, _old.dir); + oldDirNode = this.findInodeSync(tx, _old.dir, 'rename'), + oldDirList = decodeDirListing(this.getSync(tx, oldDirNode.data, _old.dir, 'rename')); if (!oldDirList[_old.base]) { throw ErrnoError.With('ENOENT', oldPath, 'rename'); } - const ino: Ino = oldDirList[_old.base]; + const ino: bigint = oldDirList[_old.base]; delete oldDirList[_old.base]; /* @@ -139,8 +140,8 @@ export class StoreFS extends FileSystem { const sameParent = _new.dir === _old.dir; // Prevent us from re-grabbing the same directory listing, which still contains `old_path.base.` - const newDirNode: Inode = sameParent ? oldDirNode : this.findINodeSync(tx, _new.dir, 'rename'); - const newDirList: typeof oldDirList = sameParent ? oldDirList : this.getDirListingSync(tx, newDirNode, _new.dir); + const newDirNode: Inode = sameParent ? oldDirNode : this.findInodeSync(tx, _new.dir, 'rename'); + const newDirList: typeof oldDirList = sameParent ? oldDirList : decodeDirListing(this.getSync(tx, newDirNode.data, _new.dir, 'rename')); if (newDirList[_new.base]) { // If it's a file, delete it, if it's a directory, throw a permissions error. @@ -148,41 +149,41 @@ export class StoreFS extends FileSystem { if (!existing.toStats().isFile()) { throw ErrnoError.With('EPERM', newPath, 'rename'); } - tx.removeSync(existing.ino); + tx.removeSync(existing.data); tx.removeSync(newDirList[_new.base]); } newDirList[_new.base] = ino; // Commit the two changed directory listings. - tx.setSync(oldDirNode.ino, encodeDirListing(oldDirList)); - tx.setSync(newDirNode.ino, encodeDirListing(newDirList)); + tx.setSync(oldDirNode.data, encodeDirListing(oldDirList)); + tx.setSync(newDirNode.data, encodeDirListing(newDirList)); tx.commitSync(); } public async stat(path: string): Promise { await using tx = this.store.transaction(); - return (await this.findINode(tx, path, 'stat')).toStats(); + return (await this.findInode(tx, path, 'stat')).toStats(); } public statSync(path: string): Stats { using tx = this.store.transaction(); - return this.findINodeSync(tx, path, 'stat').toStats(); + return this.findInodeSync(tx, path, 'stat').toStats(); } public async createFile(path: string, flag: string, mode: number): Promise { - const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(0)); - return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array(0)); + const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(), 'createFile'); + return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array()); } public createFileSync(path: string, flag: string, mode: number): File { - this.commitNewSync(path, S_IFREG, mode); - return this.openFileSync(path, flag); + const node = this.commitNewSync(path, S_IFREG, mode, new Uint8Array(), 'createFile'); + return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array()); } public async openFile(path: string, flag: string): Promise { await using tx = this.store.transaction(); - const node = await this.findINode(tx, path, 'openFile'), - data = await tx.get(node.ino); + const node = await this.findInode(tx, path, 'openFile'), + data = await tx.get(node.data); if (!data) { throw ErrnoError.With('ENOENT', path, 'openFile'); } @@ -191,8 +192,8 @@ export class StoreFS extends FileSystem { public openFileSync(path: string, flag: string): File { using tx = this.store.transaction(); - const node = this.findINodeSync(tx, path, 'openFile'), - data = tx.getSync(node.ino); + const node = this.findInodeSync(tx, path, 'openFile'), + data = tx.getSync(node.data); if (!data) { throw ErrnoError.With('ENOENT', path, 'openFile'); } @@ -222,23 +223,23 @@ export class StoreFS extends FileSystem { } public async mkdir(path: string, mode: number): Promise { - await this.commitNew(path, S_IFDIR, mode, encodeUTF8('{}')); + await this.commitNew(path, S_IFDIR, mode, encodeUTF8('{}'), 'mkdir'); } public mkdirSync(path: string, mode: number): void { - this.commitNewSync(path, S_IFDIR, mode, encodeUTF8('{}')); + this.commitNewSync(path, S_IFDIR, mode, encodeUTF8('{}'), 'mkdir'); } public async readdir(path: string): Promise { await using tx = this.store.transaction(); - const node = await this.findINode(tx, path, 'readdir'); - return Object.keys(await this.getDirListing(tx, node, path)); + const node = await this.findInode(tx, path, 'readdir'); + return Object.keys(decodeDirListing(await this.get(tx, node.data, path, 'readdir'))); } public readdirSync(path: string): string[] { using tx = this.store.transaction(); - const node = this.findINodeSync(tx, path, 'readdir'); - return Object.keys(this.getDirListingSync(tx, node, path)); + const node = this.findInodeSync(tx, path, 'readdir'); + return Object.keys(decodeDirListing(this.getSync(tx, node.data, path, 'readdir'))); } /** @@ -248,15 +249,15 @@ export class StoreFS extends FileSystem { public async sync(path: string, data: Uint8Array, stats: Readonly): Promise { await using tx = this.store.transaction(); // We use _findInode because we actually need the INode id. - const fileInodeId = await this._findINode(tx, path, 'sync'), + const fileInodeId = await this._findInode(tx, path, 'sync'), fileInode = new Inode(await this.get(tx, fileInodeId, path, 'sync')), inodeChanged = fileInode.update(stats); // Sync data. - await tx.set(fileInode.ino, data); + await tx.set(fileInode.data, data); // Sync metadata. if (inodeChanged) { - await tx.set(fileInodeId, fileInode.data); + await tx.set(fileInodeId, serialize(fileInode)); } await tx.commit(); @@ -269,15 +270,15 @@ export class StoreFS extends FileSystem { public syncSync(path: string, data: Uint8Array, stats: Readonly): void { using tx = this.store.transaction(); // We use _findInode because we actually need the INode id. - const fileInodeId = this._findINodeSync(tx, path, 'sync'), + const fileInodeId = this._findInodeSync(tx, path, 'sync'), fileInode = new Inode(this.getSync(tx, fileInodeId, path, 'sync')), inodeChanged = fileInode.update(stats); // Sync data. - tx.setSync(fileInode.ino, data); + tx.setSync(fileInode.data, data); // Sync metadata. if (inodeChanged) { - tx.setSync(fileInodeId, fileInode.data); + tx.setSync(fileInodeId, serialize(fileInode)); } tx.commitSync(); @@ -287,17 +288,17 @@ export class StoreFS extends FileSystem { await using tx = this.store.transaction(); const newDir: string = dirname(link), - newDirNode = await this.findINode(tx, newDir, 'link'), - listing = await this.getDirListing(tx, newDirNode, newDir); + newDirNode = await this.findInode(tx, newDir, 'link'), + listing = decodeDirListing(await this.get(tx, newDirNode.data, newDir, 'link')); - const ino = await this._findINode(tx, target, 'link'); + const ino = await this._findInode(tx, target, 'link'); const node = new Inode(await this.get(tx, ino, target, 'link')); node.nlink++; listing[basename(link)] = ino; - tx.setSync(ino, node.data); - tx.setSync(newDirNode.ino, encodeDirListing(listing)); + tx.setSync(ino, serialize(node)); + tx.setSync(newDirNode.data, encodeDirListing(listing)); tx.commitSync(); } @@ -305,17 +306,17 @@ export class StoreFS extends FileSystem { using tx = this.store.transaction(); const newDir: string = dirname(link), - newDirNode = this.findINodeSync(tx, newDir, 'link'), - listing = this.getDirListingSync(tx, newDirNode, newDir); + newDirNode = this.findInodeSync(tx, newDir, 'link'), + listing = decodeDirListing(this.getSync(tx, newDirNode.data, newDir, 'link')); - const ino = this._findINodeSync(tx, target, 'link'); + const ino = this._findInodeSync(tx, target, 'link'); const node = new Inode(this.getSync(tx, ino, target, 'link')); node.nlink++; listing[basename(link)] = ino; - tx.setSync(ino, node.data); - tx.setSync(newDirNode.ino, encodeDirListing(listing)); + tx.setSync(ino, serialize(node)); + tx.setSync(newDirNode.data, encodeDirListing(listing)); tx.commitSync(); } @@ -329,10 +330,11 @@ export class StoreFS extends FileSystem { } // Create new inode. o777, owned by root:root const inode = new Inode(); + inode.ino = rootIno; inode.mode = 0o777 | S_IFDIR; // If the root doesn't exist, the first random ID shouldn't exist either. - await tx.set(inode.ino, encodeUTF8('{}')); - await tx.set(rootIno, inode.data); + await tx.set(inode.data, encodeUTF8('{}')); + await tx.set(rootIno, serialize(inode)); await tx.commit(); } @@ -346,10 +348,11 @@ export class StoreFS extends FileSystem { } // Create new inode, mode o777, owned by root:root const inode = new Inode(); + inode.ino = rootIno; inode.mode = 0o777 | S_IFDIR; // If the root doesn't exist, the first random ID shouldn't exist either. - tx.setSync(inode.ino, encodeUTF8('{}')); - tx.setSync(rootIno, inode.data); + tx.setSync(inode.data, encodeUTF8('{}')); + tx.setSync(rootIno, serialize(inode)); tx.commitSync(); } @@ -359,7 +362,7 @@ export class StoreFS extends FileSystem { * @param filename The filename of the inode we are attempting to find, minus * the parent. */ - private async _findINode(tx: Transaction, path: string, syscall: string, visited: Set = new Set()): Promise { + private async _findInode(tx: Transaction, path: string, syscall: string, visited: Set = new Set()): Promise { if (visited.has(path)) { throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', path); } @@ -371,8 +374,8 @@ export class StoreFS extends FileSystem { } const { dir: parent, base: filename } = parse(path); - const inode = parent == '/' ? new Inode(await this.get(tx, rootIno, parent, syscall)) : await this.findINode(tx, parent, syscall, visited); - const dirList = await this.getDirListing(tx, inode, parent); + const inode = parent == '/' ? new Inode(await this.get(tx, rootIno, parent, syscall)) : await this.findInode(tx, parent, syscall, visited); + const dirList = decodeDirListing(await this.get(tx, inode.data, parent, syscall)); if (!(filename in dirList)) { throw ErrnoError.With('ENOENT', resolve(parent, filename), syscall); @@ -388,7 +391,7 @@ export class StoreFS extends FileSystem { * the parent. * @return string The ID of the file's inode in the file system. */ - protected _findINodeSync(tx: Transaction, path: string, syscall: string, visited: Set = new Set()): Ino { + protected _findInodeSync(tx: Transaction, path: string, syscall: string, visited: Set = new Set()): bigint { if (visited.has(path)) { throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', path); } @@ -400,8 +403,8 @@ export class StoreFS extends FileSystem { } const { dir: parent, base: filename } = parse(path); - const inode = parent == '/' ? new Inode(this.getSync(tx, rootIno, parent, syscall)) : this.findINodeSync(tx, parent, syscall, visited); - const dir = this.getDirListingSync(tx, inode, parent); + const inode = parent == '/' ? new Inode(this.getSync(tx, rootIno, parent, syscall)) : this.findInodeSync(tx, parent, syscall, visited); + const dir = decodeDirListing(this.getSync(tx, inode.data, parent, syscall)); if (!(filename in dir)) { throw ErrnoError.With('ENOENT', resolve(parent, filename), syscall); @@ -415,8 +418,8 @@ export class StoreFS extends FileSystem { * @param path The path to look up. * @todo memoize/cache */ - private async findINode(tx: Transaction, path: string, syscall: string, visited: Set = new Set()): Promise { - const ino = await this._findINode(tx, path, syscall, visited); + private async findInode(tx: Transaction, path: string, syscall: string, visited: Set = new Set()): Promise { + const ino = await this._findInode(tx, path, syscall, visited); return new Inode(await this.get(tx, ino, path, syscall)); } @@ -426,8 +429,8 @@ export class StoreFS extends FileSystem { * @return The Inode of the path p. * @todo memoize/cache */ - protected findINodeSync(tx: Transaction, path: string, syscall: string, visited: Set = new Set()): Inode { - const ino = this._findINodeSync(tx, path, syscall, visited); + protected findInodeSync(tx: Transaction, path: string, syscall: string, visited: Set = new Set()): Inode { + const ino = this._findInodeSync(tx, path, syscall, visited); return new Inode(this.getSync(tx, ino, path, syscall)); } @@ -437,7 +440,7 @@ export class StoreFS extends FileSystem { * @param path The corresponding path to the file (used for error messages). * @param id The ID to look up. */ - private async get(tx: Transaction, ino: Ino, path: string, syscall: string): Promise { + private async get(tx: Transaction, ino: bigint, path: string, syscall: string): Promise { const data = await tx.get(ino); if (!data) { throw ErrnoError.With('ENOENT', path, syscall); @@ -451,7 +454,7 @@ export class StoreFS extends FileSystem { * @param path The corresponding path to the file (used for error messages). * @param ino The ID to look up. */ - protected getSync(tx: Transaction, ino: Ino, path: string, syscall: string): Uint8Array { + private getSync(tx: Transaction, ino: bigint, path: string, syscall: string): Uint8Array { const data = tx.getSync(ino); if (!data) { throw ErrnoError.With('ENOENT', path, syscall); @@ -459,65 +462,35 @@ export class StoreFS extends FileSystem { return data; } - /** - * Given the Inode of a directory, retrieves the corresponding directory - * listing. - */ - private async getDirListing(tx: Transaction, inode: Inode, path: string): Promise<{ [fileName: string]: Ino }> { - const data = await tx.get(inode.ino); - /* - Occurs when data is undefined,or corresponds to something other than a directory listing. - The latter should never occur unless the file system is corrupted. - */ - if (!data) { - throw ErrnoError.With('ENOENT', path, 'getDirListing'); - } - - return decodeDirListing(data); - } - - /** - * Given the Inode of a directory, retrieves the corresponding directory listing. - */ - protected getDirListingSync(tx: Transaction, inode: Inode, p?: string): { [fileName: string]: Ino } { - const data = tx.getSync(inode.ino); - if (!data) { - throw ErrnoError.With('ENOENT', p, 'getDirListing'); - } - return decodeDirListing(data); - } - /** * Adds a new node under a random ID. Retries before giving up in - * the exceedingly unlikely chance that we try to reuse a random ino. + * the exceedingly unlikely chance that we try to reuse a random id. */ - private async addNew(tx: Transaction, data: Uint8Array, path: string): Promise { + private async allocNew(tx: Transaction, path: string, syscall: string): Promise { for (let i = 0; i < maxInodeAllocTries; i++) { - const ino: Ino = randomIno(); + const ino: bigint = randomBigInt(); if (await tx.get(ino)) { continue; } - await tx.set(ino, data); return ino; } - throw new ErrnoError(Errno.ENOSPC, 'No inode IDs available', path, 'addNewNode'); + throw new ErrnoError(Errno.ENOSPC, 'No IDs available', path, syscall); } /** * Creates a new node under a random ID. Retries before giving up in - * the exceedingly unlikely chance that we try to reuse a random ino. + * the exceedingly unlikely chance that we try to reuse a random id. * @return The ino that the data was stored under. */ - protected addNewSync(tx: Transaction, data: Uint8Array, path: string): Ino { + private allocNewSync(tx: Transaction, path: string, syscall: string): bigint { for (let i = 0; i < maxInodeAllocTries; i++) { - const ino: Ino = randomIno(); + const ino: bigint = randomBigInt(); if (tx.getSync(ino)) { continue; } - tx.setSync(ino, data); return ino; } - throw new ErrnoError(Errno.ENOSPC, 'No inode IDs available', path, 'addNewNode'); + throw new ErrnoError(Errno.ENOSPC, 'No IDs available', path, syscall); } /** @@ -528,7 +501,7 @@ export class StoreFS extends FileSystem { * @param mode The mode to create the new file with. * @param data The data to store at the file's data node. */ - private async commitNew(path: string, type: FileType, mode: number, data: Uint8Array = new Uint8Array(), syscall: string = '[[commitNew]]'): Promise { + private async commitNew(path: string, type: FileType, mode: number, data: Uint8Array, syscall: string): Promise { /* The root always exists. If we don't check this prior to taking steps below, @@ -541,8 +514,8 @@ export class StoreFS extends FileSystem { await using tx = this.store.transaction(); const { dir: parentPath, base: fname } = parse(path); - const parent = await this.findINode(tx, parentPath, syscall); - const listing = await this.getDirListing(tx, parent, parentPath); + const parent = await this.findInode(tx, parentPath, syscall); + const listing = decodeDirListing(await this.get(tx, parent.data, parentPath, syscall)); // Check if file already exists. if (listing[fname]) { @@ -551,15 +524,18 @@ export class StoreFS extends FileSystem { // Commit data. const inode = new Inode(); - inode.ino = await this.addNew(tx, data, path); + inode.ino = await this.allocNew(tx, path, syscall); + inode.data = await this.allocNew(tx, path, syscall); inode.mode = mode | type; inode.uid = parent.mode & S_ISUID ? parent.uid : credentials.uid; inode.gid = parent.mode & S_ISGID ? parent.gid : credentials.gid; inode.size = data.length; + await tx.set(inode.ino, serialize(inode)); + await tx.set(inode.data, data); // Update and commit parent directory listing. - listing[fname] = await this.addNew(tx, inode.data, path); - await tx.set(parent.ino, encodeDirListing(listing)); + listing[fname] = inode.ino; + await tx.set(parent.data, encodeDirListing(listing)); await tx.commit(); return inode; } @@ -573,7 +549,7 @@ export class StoreFS extends FileSystem { * @param data The data to store at the file's data node. * @return The Inode for the new file. */ - protected commitNewSync(path: string, type: FileType, mode: number, data: Uint8Array = new Uint8Array(), syscall: string = '[[commitNew]]'): Inode { + private commitNewSync(path: string, type: FileType, mode: number, data: Uint8Array, syscall: string): Inode { /* The root always exists. If we don't check this prior to taking steps below, @@ -586,9 +562,9 @@ export class StoreFS extends FileSystem { using tx = this.store.transaction(); const { dir: parentPath, base: fname } = parse(path); - const parent = this.findINodeSync(tx, parentPath, syscall); + const parent = this.findInodeSync(tx, parentPath, syscall); - const listing = this.getDirListingSync(tx, parent, parentPath); + const listing = decodeDirListing(this.getSync(tx, parent.data, parentPath, syscall)); // Check if file already exists. if (listing[fname]) { @@ -596,17 +572,20 @@ export class StoreFS extends FileSystem { } // Commit data. - const node = new Inode(); - node.ino = this.addNewSync(tx, data, path); - node.size = data.length; - node.mode = mode | type; - node.uid = parent.mode & S_ISUID ? parent.uid : credentials.uid; - node.gid = parent.mode & S_ISGID ? parent.gid : credentials.gid; + const inode = new Inode(); + inode.ino = this.allocNewSync(tx, path, syscall); + inode.data = this.allocNewSync(tx, path, syscall); + inode.size = data.length; + inode.mode = mode | type; + inode.uid = parent.mode & S_ISUID ? parent.uid : credentials.uid; + inode.gid = parent.mode & S_ISGID ? parent.gid : credentials.gid; // Update and commit parent directory listing. - listing[fname] = this.addNewSync(tx, node.data, path); - tx.setSync(parent.ino, encodeDirListing(listing)); + tx.setSync(inode.ino, serialize(inode)); + tx.setSync(inode.data, data); + listing[fname] = inode.ino; + tx.setSync(parent.data, encodeDirListing(listing)); tx.commitSync(); - return node; + return inode; } /** @@ -619,8 +598,8 @@ export class StoreFS extends FileSystem { await using tx = this.store.transaction(); const { dir: parent, base: fileName } = parse(path), - parentNode = await this.findINode(tx, parent, syscall), - listing = await this.getDirListing(tx, parentNode, parent); + parentNode = await this.findInode(tx, parent, syscall), + listing = decodeDirListing(await this.get(tx, parentNode.data, parent, syscall)); if (!listing[fileName]) { throw ErrnoError.With('ENOENT', path, 'remove'); @@ -638,11 +617,11 @@ export class StoreFS extends FileSystem { throw ErrnoError.With('EISDIR', path, 'remove'); } - await tx.set(parentNode.ino, encodeDirListing(listing)); + await tx.set(parentNode.data, encodeDirListing(listing)); if (--fileNode.nlink < 1) { // remove file - await tx.remove(fileNode.ino); + await tx.remove(fileNode.data); await tx.remove(fileIno); } @@ -656,12 +635,12 @@ export class StoreFS extends FileSystem { * @param isDir Does the path belong to a directory, or a file? * @todo Update mtime. */ - protected removeSync(path: string, isDir: boolean, syscall: string): void { + private removeSync(path: string, isDir: boolean, syscall: string): void { using tx = this.store.transaction(); const { dir: parent, base: fileName } = parse(path), - parentNode = this.findINodeSync(tx, parent, syscall), - listing = this.getDirListingSync(tx, parentNode, parent), - fileIno: Ino = listing[fileName]; + parentNode = this.findInodeSync(tx, parent, syscall), + listing = decodeDirListing(this.getSync(tx, parentNode.data, parent, syscall)), + fileIno: bigint = listing[fileName]; if (!fileIno) { throw ErrnoError.With('ENOENT', path, 'remove'); @@ -678,11 +657,11 @@ export class StoreFS extends FileSystem { } // Update directory listing. - tx.setSync(parentNode.ino, encodeDirListing(listing)); + tx.setSync(parentNode.data, encodeDirListing(listing)); if (--fileNode.nlink < 1) { // remove file - tx.removeSync(fileNode.ino); + tx.removeSync(fileNode.data); tx.removeSync(fileIno); } diff --git a/src/backends/store/simple.ts b/src/backends/store/simple.ts index 7636cde8..2797cbe0 100644 --- a/src/backends/store/simple.ts +++ b/src/backends/store/simple.ts @@ -1,14 +1,13 @@ -import type { Ino } from '../../inode.js'; import { SyncTransaction, type Store } from './store.js'; /** * An interface for simple synchronous stores that don't have special support for transactions and such. */ export interface SimpleSyncStore extends Store { - keys(): Iterable; - get(ino: Ino): Uint8Array | undefined; - set(ino: Ino, data: Uint8Array): void; - delete(ino: Ino): void; + keys(): Iterable; + get(id: bigint): Uint8Array | undefined; + set(id: bigint, data: Uint8Array): void; + delete(id: bigint): void; } /** @@ -18,33 +17,33 @@ export interface SimpleSyncStore extends Store { export abstract class SimpleAsyncStore implements SimpleSyncStore { public abstract name: string; - protected cache: Map = new Map(); + protected cache: Map = new Map(); protected queue: Set> = new Set(); - protected abstract entries(): Promise>; + protected abstract entries(): Promise>; - public keys(): Iterable { + public keys(): Iterable { return this.cache.keys(); } - public get(ino: Ino): Uint8Array | undefined { - return this.cache.get(ino); + public get(id: bigint): Uint8Array | undefined { + return this.cache.get(id); } - public set(ino: Ino, data: Uint8Array): void { - this.cache.set(ino, data); - this.queue.add(this._set(ino, data)); + public set(id: bigint, data: Uint8Array): void { + this.cache.set(id, data); + this.queue.add(this._set(id, data)); } - protected abstract _set(ino: Ino, data: Uint8Array): Promise; + protected abstract _set(ino: bigint, data: Uint8Array): Promise; - public delete(ino: Ino): void { - this.cache.delete(ino); - this.queue.add(this._delete(ino)); + public delete(id: bigint): void { + this.cache.delete(id); + this.queue.add(this._delete(id)); } - protected abstract _delete(ino: Ino): Promise; + protected abstract _delete(ino: bigint): Promise; public clearSync(): void { this.cache.clear(); @@ -79,32 +78,32 @@ export class SimpleTransaction extends SyncTransaction { * Stores data in the keys we modify prior to modifying them. * Allows us to roll back commits. */ - protected originalData: Map = new Map(); + protected originalData: Map = new Map(); /** * List of keys modified in this transaction, if any. */ - protected modifiedKeys: Set = new Set(); + protected modifiedKeys: Set = new Set(); protected declare store: SimpleSyncStore; - public keysSync(): Iterable { + public keysSync(): Iterable { return this.store.keys(); } - public getSync(ino: Ino): Uint8Array { - const val = this.store.get(ino); - this.stashOldValue(ino, val); + public getSync(id: bigint): Uint8Array { + const val = this.store.get(id); + this.stashOldValue(id, val); return val!; } - public setSync(ino: Ino, data: Uint8Array): void { - this.markModified(ino); - return this.store.set(ino, data); + public setSync(id: bigint, data: Uint8Array): void { + this.markModified(id); + return this.store.set(id, data); } - public removeSync(ino: Ino): void { - this.markModified(ino); - this.store.delete(ino); + public removeSync(id: bigint): void { + this.markModified(id); + this.store.delete(id); } public commitSync(): void { @@ -135,10 +134,10 @@ export class SimpleTransaction extends SyncTransaction { * prevent needless `get` requests if the program modifies the data later * on during the transaction. */ - protected stashOldValue(ino: Ino, value?: Uint8Array): void { + protected stashOldValue(id: bigint, value?: Uint8Array): void { // Keep only the earliest value in the transaction. - if (!this.originalData.has(ino)) { - this.originalData.set(ino, value); + if (!this.originalData.has(id)) { + this.originalData.set(id, value); } } @@ -146,10 +145,10 @@ export class SimpleTransaction extends SyncTransaction { * Marks `ino` as modified, and stashes its value if it has not been * stashed already. */ - protected markModified(ino: Ino): void { - this.modifiedKeys.add(ino); - if (!this.originalData.has(ino)) { - this.originalData.set(ino, this.store.get(ino)); + protected markModified(id: bigint): void { + this.modifiedKeys.add(id); + if (!this.originalData.has(id)) { + this.originalData.set(id, this.store.get(id)); } } } diff --git a/src/backends/store/store.ts b/src/backends/store/store.ts index d7f30119..cd65d670 100644 --- a/src/backends/store/store.ts +++ b/src/backends/store/store.ts @@ -1,5 +1,4 @@ import { ErrnoError } from '../../error.js'; -import type { Ino } from '../../inode.js'; import '../../polyfills.js'; /** @@ -46,52 +45,52 @@ export abstract class Transaction { /** * Gets all of the keys */ - public abstract keys(): Promise>; + public abstract keys(): Promise>; /** * Gets all of the keys */ - public abstract keysSync(): Iterable; + public abstract keysSync(): Iterable; /** - * Retrieves the data at `ino`. - * @param ino The key to look under for data. + * Retrieves data. + * @param id The key to look under for data. */ - public abstract get(ino: Ino): Promise; + public abstract get(id: bigint): Promise; /** - * Retrieves the data at `ino`. + * Retrieves data. * Throws an error if an error occurs or if the key does not exist. - * @param ino The key to look under for data. + * @param id The key to look under for data. * @return The data stored under the key, or undefined if not present. */ - public abstract getSync(ino: Ino): Uint8Array; + public abstract getSync(id: bigint): Uint8Array; /** - * Adds the data to the store under `ino`. Overwrites any existing data. - * @param ino The key to add the data under. + * Adds the data to the store under an id. Overwrites any existing data. + * @param id The key to add the data under. * @param data The data to add to the store. */ - public abstract set(ino: Ino, data: Uint8Array): Promise; + public abstract set(id: bigint, data: Uint8Array): Promise; /** - * Adds the data to the store under `ino`. - * @param ino The key to add the data under. + * Adds the data to the store under and id. + * @param id The key to add the data under. * @param data The data to add to the store. */ - public abstract setSync(ino: Ino, data: Uint8Array): void; + public abstract setSync(id: bigint, data: Uint8Array): void; /** * Deletes the data at `ino`. - * @param ino The key to delete from the store. + * @param id The key to delete from the store. */ - public abstract remove(ino: Ino): Promise; + public abstract remove(id: bigint): Promise; /** * Deletes the data at `ino`. - * @param ino The key to delete from the store. + * @param id The key to delete from the store. */ - public abstract removeSync(ino: Ino): void; + public abstract removeSync(id: bigint): void; /** * Commits the transaction. @@ -135,19 +134,19 @@ export abstract class Transaction { */ export abstract class SyncTransaction extends Transaction { /* eslint-disable @typescript-eslint/require-await */ - public async keys(): Promise> { + public async keys(): Promise> { return this.keysSync(); } - public async get(ino: Ino): Promise { - return this.getSync(ino); + public async get(id: bigint): Promise { + return this.getSync(id); } - public async set(ino: bigint, data: Uint8Array): Promise { - return this.setSync(ino, data); + public async set(id: bigint, data: Uint8Array): Promise { + return this.setSync(id, data); } - public async remove(ino: Ino): Promise { - return this.removeSync(ino); + public async remove(id: bigint): Promise { + return this.removeSync(id); } public async commit(): Promise { diff --git a/src/devices.ts b/src/devices.ts index ae11b82b..ce61960b 100644 --- a/src/devices.ts +++ b/src/devices.ts @@ -11,7 +11,6 @@ import { File } from './file.js'; import type { StatsLike } from './stats.js'; import { Stats } from './stats.js'; import { basename, dirname } from './emulation/path.js'; -import type { Ino } from './inode.js'; /** * A device @@ -28,7 +27,7 @@ export interface Device { /** * Which inode the device is assigned */ - ino: Ino; + ino: bigint; /** * Data associated with a device. @@ -70,7 +69,7 @@ export interface DeviceDriver { * @returns `Device.data` * @experimental */ - init?(ino: Ino): { + init?(ino: bigint): { data?: TData; minor?: number; major?: number; diff --git a/src/inode.ts b/src/inode.ts index c20b8e7c..a014b774 100644 --- a/src/inode.ts +++ b/src/inode.ts @@ -1,11 +1,6 @@ -import { deserialize, serialize, sizeof, struct, types as t } from 'utilium'; +import { deserialize, sizeof, struct, types as t } from 'utilium'; import { Stats, type StatsLike } from './stats.js'; - -/** - * Alias for an ino. - * This will be helpful if in the future inode numbers/IDs are changed to strings or numbers. - */ -export type Ino = bigint; +import { randomBigInt } from './utils.js'; /** * Room inode @@ -13,30 +8,12 @@ export type Ino = bigint; */ export const rootIno = 0n; -/** - * Generates a random 32 bit integer, then converts to a hex string - */ -function _random() { - return Math.round(Math.random() * 2 ** 32).toString(16); -} - -/** - * Generate a random ino - * @internal - */ -export function randomIno(): Ino { - return BigInt('0x' + _random() + _random()); -} - /** * Generic inode definition that can easily be serialized. + * @internal */ @struct() export class Inode implements StatsLike { - public get data(): Uint8Array { - return serialize(this); - } - public constructor(buffer?: ArrayBufferLike | ArrayBufferView) { if (buffer) { if (buffer.byteLength < sizeof(Inode)) { @@ -48,7 +25,8 @@ export class Inode implements StatsLike { } // set defaults on a fresh inode - this.ino = randomIno(); + this.ino = randomBigInt(); + this.data = randomBigInt(); this.nlink = 1; this.size = 4096; const now = Date.now(); @@ -58,7 +36,7 @@ export class Inode implements StatsLike { this.birthtimeMs = now; } - @t.uint64 public ino!: Ino; + @t.uint64 public data!: bigint; @t.uint32 public size!: number; @t.uint16 public mode!: number; @t.uint32 public nlink!: number; @@ -68,6 +46,7 @@ export class Inode implements StatsLike { @t.float64 public birthtimeMs!: number; @t.float64 public mtimeMs!: number; @t.float64 public ctimeMs!: number; + @t.uint64 public ino!: bigint; /** * Handy function that converts the Inode to a Node Stats object. diff --git a/src/utils.ts b/src/utils.ts index 2f55a6ee..e2aadbcd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -266,3 +266,11 @@ export function normalizeOptions( } export type Concrete = Pick & (new (...args: any[]) => InstanceType); + +/** + * Generate a random ino + * @internal + */ +export function randomBigInt(): bigint { + return crypto.getRandomValues(new BigUint64Array(1))[0]; +}