UNPKG

@zenfs/core

Version:

A filesystem, anywhere

414 lines (413 loc) 13.4 kB
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { if (value !== null && value !== void 0) { if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); var dispose, inner; if (async) { if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); dispose = value[Symbol.asyncDispose]; } if (dispose === void 0) { if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); dispose = value[Symbol.dispose]; if (async) inner = dispose; } if (typeof dispose !== "function") throw new TypeError("Object not disposable."); if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; env.stack.push({ value: value, dispose: dispose, async: async }); } else if (async) { env.stack.push({ async: true }); } return value; }; var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { return function (env) { function fail(e) { env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; env.hasError = true; } function next() { while (env.stack.length) { var rec = env.stack.pop(); try { var result = rec.dispose && rec.dispose.call(rec.value); if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); } catch (e) { fail(e); } } if (env.hasError) throw env.error; } return next(); }; })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }); import { InMemoryStore } from './backends/memory.js'; import { StoreFS } from './backends/store/fs.js'; import { S_IFBLK, S_IFCHR } from './emulation/constants.js'; import { Errno, ErrnoError } from './error.js'; import { File } from './file.js'; import { Stats } from './stats.js'; import { basename, dirname } from './emulation/path.js'; /** * The base class for device files * This class only does some simple things: * It implements `truncate` using `write` and it has non-device methods throw. * It is up to device drivers to implement the rest of the functionality. * @experimental */ export class DeviceFile extends File { constructor(fs, path, device) { super(fs, path); this.fs = fs; this.device = device; this.position = 0; } get driver() { return this.device.driver; } get stats() { return { mode: (this.driver.isBuffered ? S_IFBLK : S_IFCHR) | 0o666 }; } async stat() { return Promise.resolve(new Stats(this.stats)); } statSync() { return new Stats(this.stats); } readSync(buffer, offset, length, position) { return this.driver.read(this, buffer, offset, length, position); } // eslint-disable-next-line @typescript-eslint/require-await async read(buffer, offset, length) { return { bytesRead: this.readSync(buffer, offset, length), buffer }; } writeSync(buffer, offset = 0, length = buffer.length, position) { return this.driver.write(this, buffer, offset, length, position); } // eslint-disable-next-line @typescript-eslint/require-await async write(buffer, offset, length, position) { return this.writeSync(buffer, offset, length, position); } async truncate(length) { const { size } = await this.stat(); const buffer = new Uint8Array(length > size ? length - size : 0); await this.write(buffer, 0, buffer.length, length > size ? size : length); } truncateSync(length) { const { size } = this.statSync(); const buffer = new Uint8Array(length > size ? length - size : 0); this.writeSync(buffer, 0, buffer.length, length > size ? size : length); } closeSync() { this.driver.close?.(this); } close() { this.closeSync(); return Promise.resolve(); } syncSync() { this.driver.sync?.(this); } sync() { this.syncSync(); return Promise.resolve(); } chown() { throw ErrnoError.With('ENOTSUP', this.path, 'chown'); } chownSync() { throw ErrnoError.With('ENOTSUP', this.path, 'chown'); } chmod() { throw ErrnoError.With('ENOTSUP', this.path, 'chmod'); } chmodSync() { throw ErrnoError.With('ENOTSUP', this.path, 'chmod'); } utimes() { throw ErrnoError.With('ENOTSUP', this.path, 'utimes'); } utimesSync() { throw ErrnoError.With('ENOTSUP', this.path, 'utimes'); } _setType() { throw ErrnoError.With('ENOTSUP', this.path, '_setType'); } _setTypeSync() { throw ErrnoError.With('ENOTSUP', this.path, '_setType'); } } /** * @experimental */ export class DeviceFS extends StoreFS { createDevice(path, driver) { if (this.existsSync(path)) { throw ErrnoError.With('EEXIST', path, 'mknod'); } let ino = 1n; while (this.store.has(ino)) ino++; const dev = { driver, ino, }; this.devices.set(path, dev); return dev; } constructor() { super(new InMemoryStore('devfs')); this.devices = new Map(); } async rename(oldPath, newPath) { if (this.devices.has(oldPath)) { throw ErrnoError.With('EPERM', oldPath, 'rename'); } if (this.devices.has(newPath)) { throw ErrnoError.With('EEXIST', newPath, 'rename'); } return super.rename(oldPath, newPath); } renameSync(oldPath, newPath) { if (this.devices.has(oldPath)) { throw ErrnoError.With('EPERM', oldPath, 'rename'); } if (this.devices.has(newPath)) { throw ErrnoError.With('EEXIST', newPath, 'rename'); } return super.renameSync(oldPath, newPath); } async stat(path) { if (this.devices.has(path)) { const env_1 = { stack: [], error: void 0, hasError: false }; try { const file = __addDisposableResource(env_1, await this.openFile(path, 'r'), true); return file.stat(); } catch (e_1) { env_1.error = e_1; env_1.hasError = true; } finally { const result_1 = __disposeResources(env_1); if (result_1) await result_1; } } return super.stat(path); } statSync(path) { if (this.devices.has(path)) { const env_2 = { stack: [], error: void 0, hasError: false }; try { const file = __addDisposableResource(env_2, this.openFileSync(path, 'r'), false); return file.statSync(); } catch (e_2) { env_2.error = e_2; env_2.hasError = true; } finally { __disposeResources(env_2); } } return super.statSync(path); } async openFile(path, flag) { if (this.devices.has(path)) { return new DeviceFile(this, path, this.devices.get(path)); } return await super.openFile(path, flag); } openFileSync(path, flag) { if (this.devices.has(path)) { return new DeviceFile(this, path, this.devices.get(path)); } return super.openFileSync(path, flag); } async createFile(path, flag, mode) { if (this.devices.has(path)) { throw ErrnoError.With('EEXIST', path, 'createFile'); } return super.createFile(path, flag, mode); } createFileSync(path, flag, mode) { if (this.devices.has(path)) { throw ErrnoError.With('EEXIST', path, 'createFile'); } return super.createFileSync(path, flag, mode); } async unlink(path) { if (this.devices.has(path)) { throw ErrnoError.With('EPERM', path, 'unlink'); } return super.unlink(path); } unlinkSync(path) { if (this.devices.has(path)) { throw ErrnoError.With('EPERM', path, 'unlink'); } return super.unlinkSync(path); } async rmdir(path) { return super.rmdir(path); } rmdirSync(path) { return super.rmdirSync(path); } async mkdir(path, mode) { if (this.devices.has(path)) { throw ErrnoError.With('EEXIST', path, 'mkdir'); } return super.mkdir(path, mode); } mkdirSync(path, mode) { if (this.devices.has(path)) { throw ErrnoError.With('EEXIST', path, 'mkdir'); } return super.mkdirSync(path, mode); } async readdir(path) { const entries = await super.readdir(path); for (const dev of this.devices.keys()) { if (dirname(dev) == path) { entries.push(basename(dev)); } } return entries; } readdirSync(path) { const entries = super.readdirSync(path); for (const dev of this.devices.keys()) { if (dirname(dev) == path) { entries.push(basename(dev)); } } return entries; } async link(target, link) { if (this.devices.has(target)) { throw ErrnoError.With('EPERM', target, 'rmdir'); } if (this.devices.has(link)) { throw ErrnoError.With('EEXIST', link, 'link'); } return super.link(target, link); } linkSync(target, link) { if (this.devices.has(target)) { throw ErrnoError.With('EPERM', target, 'rmdir'); } if (this.devices.has(link)) { throw ErrnoError.With('EEXIST', link, 'link'); } return super.linkSync(target, link); } async sync(path, data, stats) { if (this.devices.has(path)) { throw new ErrnoError(Errno.EINVAL, 'Attempted to sync a device incorrectly (bug)', path, 'sync'); } return super.sync(path, data, stats); } syncSync(path, data, stats) { if (this.devices.has(path)) { throw new ErrnoError(Errno.EINVAL, 'Attempted to sync a device incorrectly (bug)', path, 'sync'); } return super.syncSync(path, data, stats); } } function defaultWrite(file, buffer, offset, length) { file.position += length; return length; } /** * Simulates the `/dev/null` device. * - Reads return 0 bytes (EOF). * - Writes discard data, advancing the file position. * @experimental */ export const nullDevice = { name: 'null', isBuffered: false, read() { return 0; }, write: defaultWrite, }; /** * Simulates the `/dev/zero` device * Provides an infinite stream of zeroes when read. * Discards any data written to it. * * - Reads fill the buffer with zeroes. * - Writes discard data but update the file position. * - Provides basic file metadata, treating it as a character device. * @experimental */ export const zeroDevice = { name: 'zero', isBuffered: false, read(file, buffer, offset = 0, length = buffer.byteLength) { const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); for (let i = offset; i < offset + length; i++) { data[i] = 0; } file.position += length; return length; }, write: defaultWrite, }; /** * Simulates the `/dev/full` device. * - Reads behave like `/dev/zero` (returns zeroes). * - Writes always fail with ENOSPC (no space left on device). * @experimental */ export const fullDevice = { name: 'full', isBuffered: false, read(file, buffer, offset = 0, length = buffer.byteLength) { const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); for (let i = offset; i < offset + length; i++) { data[i] = 0; } file.position += length; return length; }, write(file) { throw ErrnoError.With('ENOSPC', file.path, 'write'); }, }; /** * Simulates the `/dev/random` device. * - Reads return random bytes. * - Writes discard data, advancing the file position. * @experimental */ export const randomDevice = { name: 'random', isBuffered: false, read(file, buffer, offset = 0, length = buffer.byteLength) { const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); for (let i = offset; i < offset + length; i++) { data[i] = Math.floor(Math.random() * 256); } file.position += length; return length; }, write: defaultWrite, }; /** * Shortcuts for importing. * @experimental */ export default { null: nullDevice, zero: zeroDevice, full: fullDevice, random: randomDevice, };