@zenfs/core
Advanced tools
Comparing version 0.18.0 to 0.18.1
@@ -91,7 +91,5 @@ import type { File } from '../file.js'; | ||
} | ||
declare const OverlayFS_base: import("../mixins/shared.js").Mixin<typeof UnmutexedOverlayFS, { | ||
lock(path: string, syscall: string): Promise<import("../mixins/mutexed.js").MutexLock>; | ||
lockSync(path: string): import("../mixins/mutexed.js").MutexLock; | ||
isLocked(path: string): boolean; | ||
}>; | ||
declare const OverlayFS_base: { | ||
new (): import("../mixins/mutexed.js").__MutexedFS<UnmutexedOverlayFS>; | ||
} & (new (args_0: OverlayOptions) => import("../mixins/mutexed.js").__MutexedFS<UnmutexedOverlayFS>); | ||
/** | ||
@@ -98,0 +96,0 @@ * OverlayFS makes a read-only filesystem writable by storing writes on a second, |
@@ -379,3 +379,4 @@ import { dirname } from '../emulation/path.js'; | ||
createParentDirectoriesSync(path) { | ||
let parent = dirname(path), toCreate = []; | ||
let parent = dirname(path); | ||
const toCreate = []; | ||
while (!this.writable.existsSync(parent)) { | ||
@@ -385,9 +386,9 @@ toCreate.push(parent); | ||
} | ||
toCreate = toCreate.reverse(); | ||
for (const p of toCreate) { | ||
this.writable.mkdirSync(p, this.statSync(p).mode); | ||
for (const path of toCreate.reverse()) { | ||
this.writable.mkdirSync(path, this.statSync(path).mode); | ||
} | ||
} | ||
async createParentDirectories(path) { | ||
let parent = dirname(path), toCreate = []; | ||
let parent = dirname(path); | ||
const toCreate = []; | ||
while (!(await this.writable.exists(parent))) { | ||
@@ -397,6 +398,5 @@ toCreate.push(parent); | ||
} | ||
toCreate = toCreate.reverse(); | ||
for (const p of toCreate) { | ||
const stats = await this.stat(p); | ||
await this.writable.mkdir(p, stats.mode); | ||
for (const path of toCreate.reverse()) { | ||
const stats = await this.stat(path); | ||
await this.writable.mkdir(path, stats.mode); | ||
} | ||
@@ -403,0 +403,0 @@ } |
@@ -20,5 +20,4 @@ /// <reference types="node" resolution-mode="require"/> | ||
export declare class PortFile extends File { | ||
readonly fs: PortFS; | ||
fs: PortFS; | ||
readonly fd: number; | ||
readonly path: string; | ||
position: number; | ||
@@ -25,0 +24,0 @@ constructor(fs: PortFS, fd: number, path: string, position: number); |
@@ -11,6 +11,5 @@ import { resolveMountConfig } from '../../config.js'; | ||
constructor(fs, fd, path, position) { | ||
super(); | ||
super(fs, path); | ||
this.fs = fs; | ||
this.fd = fd; | ||
this.path = path; | ||
this.position = position; | ||
@@ -27,3 +26,3 @@ } | ||
_throwNoSync(syscall) { | ||
throw new ErrnoError(Errno.ENOTSUP, 'Syncrohnous operations not support on PortFile', this.path, syscall); | ||
throw new ErrnoError(Errno.ENOTSUP, 'Synchronous operations not supported on PortFile', this.path, syscall); | ||
} | ||
@@ -30,0 +29,0 @@ async stat() { |
@@ -57,3 +57,2 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { credentials } from '../credentials.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
@@ -217,3 +216,3 @@ import { FSWatcher, emitChange } from './watchers.js'; | ||
const stats = await this.file.stat(); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
if (!stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', this.file.path, 'stat'); | ||
@@ -362,3 +361,3 @@ } | ||
const dst = resolveMount(newPath); | ||
if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
@@ -402,3 +401,3 @@ } | ||
const stats = await fs.stat(resolved); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
if (!stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
@@ -456,3 +455,3 @@ } | ||
try { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
@@ -484,3 +483,3 @@ } | ||
const parentStats = await fs.stat(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK, credentials)) { | ||
if (!parentStats.hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
@@ -493,3 +492,3 @@ } | ||
} | ||
if (!stats.hasAccess(flagToMode(flag), credentials)) { | ||
if (!stats.hasAccess(flagToMode(flag))) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
@@ -620,3 +619,3 @@ } | ||
try { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'rmdir'); | ||
@@ -641,3 +640,3 @@ } | ||
if (!options?.recursive) { | ||
if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
@@ -655,3 +654,3 @@ } | ||
for (const dir of dirs) { | ||
if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
@@ -671,3 +670,3 @@ } | ||
path = normalizePath(path); | ||
if (!(await stat(path)).hasAccess(constants.R_OK, credentials)) { | ||
if (!(await stat(path)).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
@@ -709,7 +708,7 @@ } | ||
targetPath = normalizePath(targetPath); | ||
if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK, credentials)) { | ||
if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
@@ -723,3 +722,3 @@ } | ||
try { | ||
if (!(await fs.stat(path)).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(path)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
@@ -954,3 +953,3 @@ } | ||
const stats = await stat(path); | ||
if (!stats.hasAccess(mode, credentials)) { | ||
if (!stats.hasAccess(mode)) { | ||
throw new ErrnoError(Errno.EACCES); | ||
@@ -957,0 +956,0 @@ } |
@@ -55,3 +55,2 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { credentials } from '../credentials.js'; | ||
import { emitChange } from './watchers.js'; | ||
@@ -68,3 +67,3 @@ /** | ||
const newMount = resolveMount(newPath); | ||
if (!statSync(dirname(oldPath)).hasAccess(constants.W_OK, credentials)) { | ||
if (!statSync(dirname(oldPath)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
@@ -110,3 +109,3 @@ } | ||
const stats = fs.statSync(resolved); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
if (!stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
@@ -165,3 +164,3 @@ } | ||
try { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
@@ -188,3 +187,3 @@ } | ||
const parentStats = fs.statSync(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK, credentials)) { | ||
if (!parentStats.hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
@@ -198,3 +197,3 @@ } | ||
const stats = fs.statSync(resolved); | ||
if (!stats.hasAccess(mode, credentials) || !stats.hasAccess(flagToMode(flag), credentials)) { | ||
if (!stats.hasAccess(mode) || !stats.hasAccess(flagToMode(flag))) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
@@ -449,3 +448,3 @@ } | ||
try { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'rmdir'); | ||
@@ -470,3 +469,3 @@ } | ||
if (!options?.recursive) { | ||
if (!fs.statSync(dirname(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(dirname(resolved)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
@@ -482,3 +481,3 @@ } | ||
for (const dir of dirs) { | ||
if (!fs.statSync(dirname(dir)).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(dirname(dir)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
@@ -500,3 +499,3 @@ } | ||
let entries; | ||
if (!statSync(path).hasAccess(constants.R_OK, credentials)) { | ||
if (!statSync(path).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
@@ -540,7 +539,7 @@ } | ||
targetPath = normalizePath(targetPath); | ||
if (!statSync(dirname(targetPath)).hasAccess(constants.R_OK, credentials)) { | ||
if (!statSync(dirname(targetPath)).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!statSync(dirname(linkPath)).hasAccess(constants.W_OK, credentials)) { | ||
if (!statSync(dirname(linkPath)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
@@ -554,3 +553,3 @@ } | ||
try { | ||
if (!fs.statSync(path).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(path).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
@@ -687,3 +686,3 @@ } | ||
const stats = statSync(path); | ||
if (!stats.hasAccess(mode, credentials)) { | ||
if (!stats.hasAccess(mode)) { | ||
throw new ErrnoError(Errno.EACCES); | ||
@@ -690,0 +689,0 @@ } |
@@ -40,10 +40,25 @@ /// <reference types="node" resolution-mode="require"/> | ||
/** | ||
* Get the current file position. | ||
* @internal | ||
* The file system that created the file | ||
*/ | ||
abstract position: number; | ||
fs: FileSystem; | ||
/** | ||
* The path to the file | ||
*/ | ||
abstract readonly path: string; | ||
readonly path: string; | ||
constructor( | ||
/** | ||
* @internal | ||
* The file system that created the file | ||
*/ | ||
fs: FileSystem, | ||
/** | ||
* The path to the file | ||
*/ | ||
path: string); | ||
/** | ||
* Get the current file position. | ||
*/ | ||
abstract position: number; | ||
/** | ||
* Asynchronous `stat`. | ||
@@ -187,8 +202,5 @@ */ | ||
* The file system that created the file. | ||
* @internal | ||
*/ | ||
protected fs: FS; | ||
/** | ||
* Path to the file | ||
*/ | ||
readonly path: string; | ||
fs: FS; | ||
readonly flag: string; | ||
@@ -224,9 +236,6 @@ readonly stats: Stats; | ||
* The file system that created the file. | ||
* @internal | ||
*/ | ||
fs: FS, | ||
fs: FS, path: string, flag: string, stats: Stats, _buffer?: Uint8Array); | ||
/** | ||
* Path to the file | ||
*/ | ||
path: string, flag: string, stats: Stats, _buffer?: Uint8Array); | ||
/** | ||
* Get the underlying buffer for this file. Mutating not recommended and will mess up dirty tracking. | ||
@@ -233,0 +242,0 @@ */ |
@@ -108,2 +108,15 @@ import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT } from './emulation/constants.js'; | ||
export class File { | ||
constructor( | ||
/** | ||
* @internal | ||
* The file system that created the file | ||
*/ | ||
fs, | ||
/** | ||
* The path to the file | ||
*/ | ||
path) { | ||
this.fs = fs; | ||
this.path = path; | ||
} | ||
[Symbol.asyncDispose]() { | ||
@@ -153,11 +166,7 @@ return this.close(); | ||
* The file system that created the file. | ||
* @internal | ||
*/ | ||
fs, | ||
/** | ||
* Path to the file | ||
*/ | ||
path, flag, stats, _buffer = new Uint8Array(new ArrayBuffer(0, fs.metadata().noResizableBuffers ? {} : { maxByteLength: size_max }))) { | ||
super(); | ||
fs, path, flag, stats, _buffer = new Uint8Array(new ArrayBuffer(0, fs.metadata().noResizableBuffers ? {} : { maxByteLength: size_max }))) { | ||
super(fs, path); | ||
this.fs = fs; | ||
this.path = path; | ||
this.flag = flag; | ||
@@ -164,0 +173,0 @@ this.stats = stats; |
@@ -1,6 +0,7 @@ | ||
import type { FileSystem } from '../filesystem.js'; | ||
import type { File } from '../file.js'; | ||
import type { FileSystem, FileSystemMetadata } from '../filesystem.js'; | ||
import '../polyfills.js'; | ||
import type { Mixin } from './shared.js'; | ||
import type { Stats } from '../stats.js'; | ||
import type { Concrete } from '../utils.js'; | ||
export declare class MutexLock { | ||
readonly path: string; | ||
protected readonly previous?: MutexLock | undefined; | ||
@@ -10,3 +11,3 @@ protected current: PromiseWithResolvers<void>; | ||
get isLocked(): boolean; | ||
constructor(path: string, previous?: MutexLock | undefined); | ||
constructor(previous?: MutexLock | undefined); | ||
done(): Promise<void>; | ||
@@ -17,2 +18,60 @@ unlock(): void; | ||
/** | ||
* @hidden | ||
*/ | ||
export declare class __MutexedFS<T extends FileSystem> implements FileSystem { | ||
/** | ||
* @internal | ||
*/ | ||
_fs: T; | ||
ready(): Promise<void>; | ||
metadata(): FileSystemMetadata; | ||
/** | ||
* The current locks | ||
*/ | ||
private currentLock?; | ||
/** | ||
* Adds a lock for a path | ||
*/ | ||
protected addLock(): MutexLock; | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, waits for it to be unlocked. | ||
* @internal | ||
*/ | ||
lock(path: string, syscall: string): Promise<MutexLock>; | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, an error will be thrown | ||
* @internal | ||
*/ | ||
lockSync(path: string, syscall: string): MutexLock; | ||
/** | ||
* Whether `path` is locked | ||
* @internal | ||
*/ | ||
get isLocked(): boolean; | ||
rename(oldPath: string, newPath: string): Promise<void>; | ||
renameSync(oldPath: string, newPath: string): void; | ||
stat(path: string): Promise<Stats>; | ||
statSync(path: string): Stats; | ||
openFile(path: string, flag: string): Promise<File>; | ||
openFileSync(path: string, flag: string): File; | ||
createFile(path: string, flag: string, mode: number): Promise<File>; | ||
createFileSync(path: string, flag: string, mode: number): File; | ||
unlink(path: string): Promise<void>; | ||
unlinkSync(path: string): void; | ||
rmdir(path: string): Promise<void>; | ||
rmdirSync(path: string): void; | ||
mkdir(path: string, mode: number): Promise<void>; | ||
mkdirSync(path: string, mode: number): void; | ||
readdir(path: string): Promise<string[]>; | ||
readdirSync(path: string): string[]; | ||
exists(path: string): Promise<boolean>; | ||
existsSync(path: string): boolean; | ||
link(srcpath: string, dstpath: string): Promise<void>; | ||
linkSync(srcpath: string, dstpath: string): void; | ||
sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>; | ||
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void; | ||
} | ||
/** | ||
* This serializes access to an underlying async filesystem. | ||
@@ -26,3 +85,8 @@ * For example, on an OverlayFS instance with an async lower | ||
* | ||
* Note: `@ts-expect-error 2513` is needed because `FS` is not properly detected as being concrete | ||
* Note: | ||
* Instead of extending the passed class, `MutexedFS` stores it internally. | ||
* This is to avoid a deadlock caused when a mathod calls another one | ||
* The problem is discussed extensivly in [#78](https://github.com/zen-fs/core/issues/78) | ||
* Instead of extending `FileSystem`, | ||
* `MutexedFS` implements it in order to make sure all of the methods are passed through | ||
* | ||
@@ -32,6 +96,4 @@ * @todo Change `using _` to `using void` pending https://github.com/tc39/proposal-discard-binding | ||
*/ | ||
export declare function Mutexed<T extends new (...args: any[]) => FileSystem>(FS: T): Mixin<T, { | ||
lock(path: string, syscall: string): Promise<MutexLock>; | ||
lockSync(path: string): MutexLock; | ||
isLocked(path: string): boolean; | ||
}>; | ||
export declare function Mutexed<const T extends Concrete<typeof FileSystem>>(FS: T): typeof __MutexedFS<InstanceType<T>> & { | ||
new (...args: ConstructorParameters<T>): __MutexedFS<InstanceType<T>>; | ||
}; |
@@ -52,4 +52,3 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
} | ||
constructor(path, previous) { | ||
this.path = path; | ||
constructor(previous) { | ||
this.previous = previous; | ||
@@ -72,404 +71,401 @@ this.current = Promise.withResolvers(); | ||
/** | ||
* This serializes access to an underlying async filesystem. | ||
* For example, on an OverlayFS instance with an async lower | ||
* directory operations like rename and rmdir may involve multiple | ||
* requests involving both the upper and lower filesystems -- they | ||
* are not executed in a single atomic step. OverlayFS uses this | ||
* to avoid having to reason about the correctness of | ||
* multiple requests interleaving. | ||
* | ||
* Note: `@ts-expect-error 2513` is needed because `FS` is not properly detected as being concrete | ||
* | ||
* @todo Change `using _` to `using void` pending https://github.com/tc39/proposal-discard-binding | ||
* @internal | ||
* @hidden | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function Mutexed(FS) { | ||
class MutexedFS extends FS { | ||
constructor() { | ||
super(...arguments); | ||
/** | ||
* The current locks | ||
*/ | ||
this.locks = new Map(); | ||
/* eslint-enable @typescript-eslint/no-unused-vars */ | ||
export class __MutexedFS { | ||
async ready() { | ||
return await this._fs.ready(); | ||
} | ||
metadata() { | ||
return this._fs.metadata(); | ||
} | ||
/** | ||
* Adds a lock for a path | ||
*/ | ||
addLock() { | ||
const lock = new MutexLock(this.currentLock); | ||
this.currentLock = lock; | ||
return lock; | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, waits for it to be unlocked. | ||
* @internal | ||
*/ | ||
async lock(path, syscall) { | ||
const previous = this.currentLock; | ||
const lock = this.addLock(); | ||
const stack = new Error().stack; | ||
setTimeout(() => { | ||
if (lock.isLocked) { | ||
const error = ErrnoError.With('EDEADLK', path, syscall); | ||
error.stack += stack?.slice('Error'.length); | ||
throw error; | ||
} | ||
}, 5000); | ||
await previous?.done(); | ||
return lock; | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, an error will be thrown | ||
* @internal | ||
*/ | ||
lockSync(path, syscall) { | ||
if (this.currentLock) { | ||
throw ErrnoError.With('EBUSY', path, syscall); | ||
} | ||
/** | ||
* Adds a lock for a path | ||
*/ | ||
addLock(path) { | ||
const previous = this.locks.get(path); | ||
const lock = new MutexLock(path, previous?.isLocked ? previous : undefined); | ||
this.locks.set(path, lock); | ||
return lock; | ||
return this.addLock(); | ||
} | ||
/** | ||
* Whether `path` is locked | ||
* @internal | ||
*/ | ||
get isLocked() { | ||
return !!this.currentLock?.isLocked; | ||
} | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
async rename(oldPath, newPath) { | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_1, await this.lock(oldPath, 'rename'), false); | ||
await this._fs.rename(oldPath, newPath); | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, waits for it to be unlocked. | ||
* @internal | ||
*/ | ||
async lock(path, syscall) { | ||
const previous = this.locks.get(path); | ||
const lock = this.addLock(path); | ||
const stack = new Error().stack; | ||
setTimeout(() => { | ||
if (lock.isLocked) { | ||
const error = ErrnoError.With('EDEADLK', path, syscall); | ||
error.stack += stack?.slice('Error'.length); | ||
throw error; | ||
} | ||
}, 5000); | ||
await previous?.done(); | ||
return lock; | ||
catch (e_1) { | ||
env_1.error = e_1; | ||
env_1.hasError = true; | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, an error will be thrown | ||
* @internal | ||
*/ | ||
lockSync(path) { | ||
if (this.locks.has(path)) { | ||
// Non-null assertion: we already checked locks has path | ||
throw ErrnoError.With('EBUSY', path, 'lock'); | ||
} | ||
return this.addLock(path); | ||
finally { | ||
__disposeResources(env_1); | ||
} | ||
/** | ||
* Whether `path` is locked | ||
* @internal | ||
*/ | ||
isLocked(path) { | ||
return !!this.locks.get(path)?.isLocked; | ||
} | ||
renameSync(oldPath, newPath) { | ||
const env_2 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_2, this.lockSync(oldPath, 'rename'), false); | ||
return this._fs.renameSync(oldPath, newPath); | ||
} | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
async rename(oldPath, newPath) { | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_1, await this.lock(oldPath, 'rename'), false); | ||
// @ts-expect-error 2513 | ||
await super.rename(oldPath, newPath); | ||
} | ||
catch (e_1) { | ||
env_1.error = e_1; | ||
env_1.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_1); | ||
} | ||
catch (e_2) { | ||
env_2.error = e_2; | ||
env_2.hasError = true; | ||
} | ||
renameSync(oldPath, newPath) { | ||
const env_2 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_2, this.lockSync(oldPath), false); | ||
// @ts-expect-error 2513 | ||
return super.renameSync(oldPath, newPath); | ||
} | ||
catch (e_2) { | ||
env_2.error = e_2; | ||
env_2.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_2); | ||
} | ||
finally { | ||
__disposeResources(env_2); | ||
} | ||
async stat(path) { | ||
const env_3 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_3, await this.lock(path, 'stat'), false); | ||
// @ts-expect-error 2513 | ||
return await super.stat(path); | ||
} | ||
catch (e_3) { | ||
env_3.error = e_3; | ||
env_3.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_3); | ||
} | ||
} | ||
async stat(path) { | ||
const env_3 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_3, await this.lock(path, 'stat'), false); | ||
return await this._fs.stat(path); | ||
} | ||
statSync(path) { | ||
const env_4 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_4, this.lockSync(path), false); | ||
// @ts-expect-error 2513 | ||
return super.statSync(path); | ||
} | ||
catch (e_4) { | ||
env_4.error = e_4; | ||
env_4.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_4); | ||
} | ||
catch (e_3) { | ||
env_3.error = e_3; | ||
env_3.hasError = true; | ||
} | ||
async openFile(path, flag) { | ||
const env_5 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_5, await this.lock(path, 'openFile'), false); | ||
// @ts-expect-error 2513 | ||
return await super.openFile(path, flag); | ||
} | ||
catch (e_5) { | ||
env_5.error = e_5; | ||
env_5.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_5); | ||
} | ||
finally { | ||
__disposeResources(env_3); | ||
} | ||
openFileSync(path, flag) { | ||
const env_6 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_6, this.lockSync(path), false); | ||
// @ts-expect-error 2513 | ||
return super.openFileSync(path, flag); | ||
} | ||
catch (e_6) { | ||
env_6.error = e_6; | ||
env_6.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_6); | ||
} | ||
} | ||
statSync(path) { | ||
const env_4 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_4, this.lockSync(path, 'stat'), false); | ||
return this._fs.statSync(path); | ||
} | ||
async createFile(path, flag, mode) { | ||
const env_7 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_7, await this.lock(path, 'createFile'), false); | ||
// @ts-expect-error 2513 | ||
return await super.createFile(path, flag, mode); | ||
} | ||
catch (e_7) { | ||
env_7.error = e_7; | ||
env_7.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_7); | ||
} | ||
catch (e_4) { | ||
env_4.error = e_4; | ||
env_4.hasError = true; | ||
} | ||
createFileSync(path, flag, mode) { | ||
const env_8 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_8, this.lockSync(path), false); | ||
// @ts-expect-error 2513 | ||
return super.createFileSync(path, flag, mode); | ||
} | ||
catch (e_8) { | ||
env_8.error = e_8; | ||
env_8.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_8); | ||
} | ||
finally { | ||
__disposeResources(env_4); | ||
} | ||
async unlink(path) { | ||
const env_9 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_9, await this.lock(path, 'unlink'), false); | ||
// @ts-expect-error 2513 | ||
await super.unlink(path); | ||
} | ||
catch (e_9) { | ||
env_9.error = e_9; | ||
env_9.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_9); | ||
} | ||
} | ||
async openFile(path, flag) { | ||
const env_5 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_5, await this.lock(path, 'openFile'), false); | ||
const file = await this._fs.openFile(path, flag); | ||
file.fs = this; | ||
return file; | ||
} | ||
unlinkSync(path) { | ||
const env_10 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_10, this.lockSync(path), false); | ||
// @ts-expect-error 2513 | ||
return super.unlinkSync(path); | ||
} | ||
catch (e_10) { | ||
env_10.error = e_10; | ||
env_10.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_10); | ||
} | ||
catch (e_5) { | ||
env_5.error = e_5; | ||
env_5.hasError = true; | ||
} | ||
async rmdir(path) { | ||
const env_11 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_11, await this.lock(path, 'rmdir'), false); | ||
// @ts-expect-error 2513 | ||
await super.rmdir(path); | ||
} | ||
catch (e_11) { | ||
env_11.error = e_11; | ||
env_11.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_11); | ||
} | ||
finally { | ||
__disposeResources(env_5); | ||
} | ||
rmdirSync(path) { | ||
const env_12 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_12, this.lockSync(path), false); | ||
// @ts-expect-error 2513 | ||
return super.rmdirSync(path); | ||
} | ||
catch (e_12) { | ||
env_12.error = e_12; | ||
env_12.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_12); | ||
} | ||
} | ||
openFileSync(path, flag) { | ||
const env_6 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_6, this.lockSync(path, 'openFile'), false); | ||
const file = this._fs.openFileSync(path, flag); | ||
file.fs = this; | ||
return file; | ||
} | ||
async mkdir(path, mode) { | ||
const env_13 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_13, await this.lock(path, 'mkdir'), false); | ||
// @ts-expect-error 2513 | ||
await super.mkdir(path, mode); | ||
} | ||
catch (e_13) { | ||
env_13.error = e_13; | ||
env_13.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_13); | ||
} | ||
catch (e_6) { | ||
env_6.error = e_6; | ||
env_6.hasError = true; | ||
} | ||
mkdirSync(path, mode) { | ||
const env_14 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_14, this.lockSync(path), false); | ||
// @ts-expect-error 2513 | ||
return super.mkdirSync(path, mode); | ||
} | ||
catch (e_14) { | ||
env_14.error = e_14; | ||
env_14.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_14); | ||
} | ||
finally { | ||
__disposeResources(env_6); | ||
} | ||
async readdir(path) { | ||
const env_15 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_15, await this.lock(path, 'readdir'), false); | ||
// @ts-expect-error 2513 | ||
return await super.readdir(path); | ||
} | ||
catch (e_15) { | ||
env_15.error = e_15; | ||
env_15.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_15); | ||
} | ||
} | ||
async createFile(path, flag, mode) { | ||
const env_7 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_7, await this.lock(path, 'createFile'), false); | ||
const file = await this._fs.createFile(path, flag, mode); | ||
file.fs = this; | ||
return file; | ||
} | ||
readdirSync(path) { | ||
const env_16 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_16, this.lockSync(path), false); | ||
// @ts-expect-error 2513 | ||
return super.readdirSync(path); | ||
} | ||
catch (e_16) { | ||
env_16.error = e_16; | ||
env_16.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_16); | ||
} | ||
catch (e_7) { | ||
env_7.error = e_7; | ||
env_7.hasError = true; | ||
} | ||
async exists(path) { | ||
const env_17 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_17, await this.lock(path, 'exists'), false); | ||
return await super.exists(path); | ||
} | ||
catch (e_17) { | ||
env_17.error = e_17; | ||
env_17.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_17); | ||
} | ||
finally { | ||
__disposeResources(env_7); | ||
} | ||
existsSync(path) { | ||
const env_18 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_18, this.lockSync(path), false); | ||
return super.existsSync(path); | ||
} | ||
catch (e_18) { | ||
env_18.error = e_18; | ||
env_18.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_18); | ||
} | ||
} | ||
createFileSync(path, flag, mode) { | ||
const env_8 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_8, this.lockSync(path, 'createFile'), false); | ||
const file = this._fs.createFileSync(path, flag, mode); | ||
file.fs = this; | ||
return file; | ||
} | ||
async link(srcpath, dstpath) { | ||
const env_19 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_19, await this.lock(srcpath, 'link'), false); | ||
// @ts-expect-error 2513 | ||
await super.link(srcpath, dstpath); | ||
} | ||
catch (e_19) { | ||
env_19.error = e_19; | ||
env_19.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_19); | ||
} | ||
catch (e_8) { | ||
env_8.error = e_8; | ||
env_8.hasError = true; | ||
} | ||
linkSync(srcpath, dstpath) { | ||
const env_20 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_20, this.lockSync(srcpath), false); | ||
// @ts-expect-error 2513 | ||
return super.linkSync(srcpath, dstpath); | ||
} | ||
catch (e_20) { | ||
env_20.error = e_20; | ||
env_20.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_20); | ||
} | ||
finally { | ||
__disposeResources(env_8); | ||
} | ||
async sync(path, data, stats) { | ||
const env_21 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_21, await this.lock(path, 'sync'), false); | ||
// @ts-expect-error 2513 | ||
await super.sync(path, data, stats); | ||
} | ||
catch (e_21) { | ||
env_21.error = e_21; | ||
env_21.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_21); | ||
} | ||
} | ||
async unlink(path) { | ||
const env_9 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_9, await this.lock(path, 'unlink'), false); | ||
await this._fs.unlink(path); | ||
} | ||
syncSync(path, data, stats) { | ||
const env_22 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_22, this.lockSync(path), false); | ||
// @ts-expect-error 2513 | ||
return super.syncSync(path, data, stats); | ||
} | ||
catch (e_22) { | ||
env_22.error = e_22; | ||
env_22.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_22); | ||
} | ||
catch (e_9) { | ||
env_9.error = e_9; | ||
env_9.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_9); | ||
} | ||
} | ||
unlinkSync(path) { | ||
const env_10 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_10, this.lockSync(path, 'unlink'), false); | ||
return this._fs.unlinkSync(path); | ||
} | ||
catch (e_10) { | ||
env_10.error = e_10; | ||
env_10.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_10); | ||
} | ||
} | ||
async rmdir(path) { | ||
const env_11 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_11, await this.lock(path, 'rmdir'), false); | ||
await this._fs.rmdir(path); | ||
} | ||
catch (e_11) { | ||
env_11.error = e_11; | ||
env_11.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_11); | ||
} | ||
} | ||
rmdirSync(path) { | ||
const env_12 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_12, this.lockSync(path, 'rmdir'), false); | ||
return this._fs.rmdirSync(path); | ||
} | ||
catch (e_12) { | ||
env_12.error = e_12; | ||
env_12.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_12); | ||
} | ||
} | ||
async mkdir(path, mode) { | ||
const env_13 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_13, await this.lock(path, 'mkdir'), false); | ||
await this._fs.mkdir(path, mode); | ||
} | ||
catch (e_13) { | ||
env_13.error = e_13; | ||
env_13.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_13); | ||
} | ||
} | ||
mkdirSync(path, mode) { | ||
const env_14 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_14, this.lockSync(path, 'mkdir'), false); | ||
return this._fs.mkdirSync(path, mode); | ||
} | ||
catch (e_14) { | ||
env_14.error = e_14; | ||
env_14.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_14); | ||
} | ||
} | ||
async readdir(path) { | ||
const env_15 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_15, await this.lock(path, 'readdir'), false); | ||
return await this._fs.readdir(path); | ||
} | ||
catch (e_15) { | ||
env_15.error = e_15; | ||
env_15.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_15); | ||
} | ||
} | ||
readdirSync(path) { | ||
const env_16 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_16, this.lockSync(path, 'readdir'), false); | ||
return this._fs.readdirSync(path); | ||
} | ||
catch (e_16) { | ||
env_16.error = e_16; | ||
env_16.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_16); | ||
} | ||
} | ||
async exists(path) { | ||
const env_17 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_17, await this.lock(path, 'exists'), false); | ||
return await this._fs.exists(path); | ||
} | ||
catch (e_17) { | ||
env_17.error = e_17; | ||
env_17.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_17); | ||
} | ||
} | ||
existsSync(path) { | ||
const env_18 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_18, this.lockSync(path, 'exists'), false); | ||
return this._fs.existsSync(path); | ||
} | ||
catch (e_18) { | ||
env_18.error = e_18; | ||
env_18.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_18); | ||
} | ||
} | ||
async link(srcpath, dstpath) { | ||
const env_19 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_19, await this.lock(srcpath, 'link'), false); | ||
await this._fs.link(srcpath, dstpath); | ||
} | ||
catch (e_19) { | ||
env_19.error = e_19; | ||
env_19.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_19); | ||
} | ||
} | ||
linkSync(srcpath, dstpath) { | ||
const env_20 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_20, this.lockSync(srcpath, 'link'), false); | ||
return this._fs.linkSync(srcpath, dstpath); | ||
} | ||
catch (e_20) { | ||
env_20.error = e_20; | ||
env_20.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_20); | ||
} | ||
} | ||
async sync(path, data, stats) { | ||
const env_21 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_21, await this.lock(path, 'sync'), false); | ||
await this._fs.sync(path, data, stats); | ||
} | ||
catch (e_21) { | ||
env_21.error = e_21; | ||
env_21.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_21); | ||
} | ||
} | ||
syncSync(path, data, stats) { | ||
const env_22 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_22, this.lockSync(path, 'sync'), false); | ||
return this._fs.syncSync(path, data, stats); | ||
} | ||
catch (e_22) { | ||
env_22.error = e_22; | ||
env_22.hasError = true; | ||
} | ||
finally { | ||
__disposeResources(env_22); | ||
} | ||
} | ||
} | ||
/** | ||
* This serializes access to an underlying async filesystem. | ||
* For example, on an OverlayFS instance with an async lower | ||
* directory operations like rename and rmdir may involve multiple | ||
* requests involving both the upper and lower filesystems -- they | ||
* are not executed in a single atomic step. OverlayFS uses this | ||
* to avoid having to reason about the correctness of | ||
* multiple requests interleaving. | ||
* | ||
* Note: | ||
* Instead of extending the passed class, `MutexedFS` stores it internally. | ||
* This is to avoid a deadlock caused when a mathod calls another one | ||
* The problem is discussed extensivly in [#78](https://github.com/zen-fs/core/issues/78) | ||
* Instead of extending `FileSystem`, | ||
* `MutexedFS` implements it in order to make sure all of the methods are passed through | ||
* | ||
* @todo Change `using _` to `using void` pending https://github.com/tc39/proposal-discard-binding | ||
* @internal | ||
*/ | ||
export function Mutexed(FS) { | ||
class MutexedFS extends __MutexedFS { | ||
constructor(...args) { | ||
super(); | ||
this._fs = new FS(...args); | ||
} | ||
} | ||
return MutexedFS; | ||
} |
@@ -13,1 +13,2 @@ import type { ExtractProperties } from 'utilium'; | ||
export type _AsyncFSMethods = ExtractProperties<FileSystem, (...args: any[]) => Promise<unknown>>; | ||
export type ConcreteFS = ExtractProperties<FileSystem, any>; |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
/* | ||
@@ -2,0 +3,0 @@ Code shared by various mixins |
/// <reference types="node" resolution-mode="require"/> | ||
import type * as Node from 'fs'; | ||
import type { Credentials } from './credentials.js'; | ||
import { type Credentials } from './credentials.js'; | ||
import { S_IFDIR, S_IFLNK, S_IFREG } from './emulation/constants.js'; | ||
@@ -149,7 +149,6 @@ /** | ||
* @param mode The requested access, combination of W_OK, R_OK, and X_OK | ||
* @param cred The requesting credentials | ||
* @returns True if the request has access, false if the request does not | ||
* @internal | ||
*/ | ||
hasAccess(mode: number, cred: Credentials): boolean; | ||
hasAccess(mode: number): boolean; | ||
/** | ||
@@ -156,0 +155,0 @@ * Convert the current stats object into a credentials object |
@@ -0,1 +1,2 @@ | ||
import { credentials } from './credentials.js'; | ||
import { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRWXG, S_IRWXO, S_IRWXU } from './emulation/constants.js'; | ||
@@ -118,8 +119,7 @@ import { size_max } from './inode.js'; | ||
* @param mode The requested access, combination of W_OK, R_OK, and X_OK | ||
* @param cred The requesting credentials | ||
* @returns True if the request has access, false if the request does not | ||
* @internal | ||
*/ | ||
hasAccess(mode, cred) { | ||
if (cred.euid === 0 || cred.egid === 0) { | ||
hasAccess(mode) { | ||
if (credentials.euid === 0 || credentials.egid === 0) { | ||
//Running as root | ||
@@ -129,3 +129,3 @@ return true; | ||
// Mask for | ||
const adjusted = (cred.uid == this.uid ? S_IRWXU : 0) | (cred.gid == this.gid ? S_IRWXG : 0) | S_IRWXO; | ||
const adjusted = (credentials.uid == this.uid ? S_IRWXU : 0) | (credentials.gid == this.gid ? S_IRWXG : 0) | S_IRWXO; | ||
return (mode & this.mode & adjusted) == mode; | ||
@@ -132,0 +132,0 @@ } |
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
import type * as fs from 'node:fs'; | ||
import type { OptionalTuple } from 'utilium'; | ||
import type { ClassLike, OptionalTuple } from 'utilium'; | ||
import { type AbsolutePath } from './emulation/path.js'; | ||
@@ -84,1 +84,2 @@ import { ErrnoError } from './error.js'; | ||
}; | ||
export type Concrete<T extends ClassLike> = Pick<T, keyof T> & (new (...args: any[]) => InstanceType<T>); |
{ | ||
"name": "@zenfs/core", | ||
"version": "0.18.0", | ||
"version": "0.18.1", | ||
"description": "A filesystem, anywhere", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -20,3 +20,3 @@ # ZenFS | ||
As an added bonus, all ZenFS backends support syncrohnous operations. All of the backends included with the core are cross-platform. | ||
As an added bonus, all ZenFS backends support synchronous operations. All of the backends included with the core are cross-platform. | ||
@@ -23,0 +23,0 @@ For more information, see the [docs](https://zen-fs.github.io/core). |
@@ -435,4 +435,4 @@ import { dirname } from '../emulation/path.js'; | ||
private createParentDirectoriesSync(path: string): void { | ||
let parent = dirname(path), | ||
toCreate: string[] = []; | ||
let parent = dirname(path); | ||
const toCreate: string[] = []; | ||
while (!this.writable.existsSync(parent)) { | ||
@@ -442,6 +442,5 @@ toCreate.push(parent); | ||
} | ||
toCreate = toCreate.reverse(); | ||
for (const p of toCreate) { | ||
this.writable.mkdirSync(p, this.statSync(p).mode); | ||
for (const path of toCreate.reverse()) { | ||
this.writable.mkdirSync(path, this.statSync(path).mode); | ||
} | ||
@@ -451,4 +450,4 @@ } | ||
private async createParentDirectories(path: string): Promise<void> { | ||
let parent = dirname(path), | ||
toCreate: string[] = []; | ||
let parent = dirname(path); | ||
const toCreate: string[] = []; | ||
while (!(await this.writable.exists(parent))) { | ||
@@ -458,7 +457,6 @@ toCreate.push(parent); | ||
} | ||
toCreate = toCreate.reverse(); | ||
for (const p of toCreate) { | ||
const stats = await this.stat(p); | ||
await this.writable.mkdir(p, stats.mode); | ||
for (const path of toCreate.reverse()) { | ||
const stats = await this.stat(path); | ||
await this.writable.mkdir(path, stats.mode); | ||
} | ||
@@ -465,0 +463,0 @@ } |
@@ -25,8 +25,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
constructor( | ||
public readonly fs: PortFS, | ||
public fs: PortFS, | ||
public readonly fd: number, | ||
public readonly path: string, | ||
path: string, | ||
public position: number | ||
) { | ||
super(); | ||
super(fs, path); | ||
} | ||
@@ -47,3 +47,3 @@ | ||
protected _throwNoSync(syscall: string): never { | ||
throw new ErrnoError(Errno.ENOTSUP, 'Syncrohnous operations not support on PortFile', this.path, syscall); | ||
throw new ErrnoError(Errno.ENOTSUP, 'Synchronous operations not supported on PortFile', this.path, syscall); | ||
} | ||
@@ -50,0 +50,0 @@ |
@@ -21,3 +21,2 @@ /* eslint-disable @typescript-eslint/no-redundant-type-constituents */ | ||
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { credentials } from '../credentials.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
@@ -224,3 +223,3 @@ import { FSWatcher, emitChange } from './watchers.js'; | ||
const stats = await this.file.stat(); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
if (!stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', this.file.path, 'stat'); | ||
@@ -401,3 +400,3 @@ } | ||
const dst = resolveMount(newPath); | ||
if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
@@ -450,3 +449,3 @@ } | ||
const stats = await fs.stat(resolved); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
if (!stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
@@ -503,3 +502,3 @@ } | ||
try { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
@@ -535,3 +534,3 @@ } | ||
const parentStats: Stats = await fs.stat(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK, credentials)) { | ||
if (!parentStats.hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
@@ -545,3 +544,3 @@ } | ||
if (!stats.hasAccess(flagToMode(flag), credentials)) { | ||
if (!stats.hasAccess(flagToMode(flag))) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
@@ -673,3 +672,3 @@ } | ||
try { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'rmdir'); | ||
@@ -705,3 +704,3 @@ } | ||
if (!options?.recursive) { | ||
if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
@@ -720,3 +719,3 @@ } | ||
for (const dir of dirs) { | ||
if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
@@ -751,3 +750,3 @@ } | ||
path = normalizePath(path); | ||
if (!(await stat(path)).hasAccess(constants.R_OK, credentials)) { | ||
if (!(await stat(path)).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
@@ -790,7 +789,7 @@ } | ||
targetPath = normalizePath(targetPath); | ||
if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK, credentials)) { | ||
if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
@@ -805,3 +804,3 @@ } | ||
try { | ||
if (!(await fs.stat(path)).hasAccess(constants.W_OK, credentials)) { | ||
if (!(await fs.stat(path)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
@@ -985,3 +984,3 @@ } | ||
const stats = await stat(path); | ||
if (!stats.hasAccess(mode, credentials)) { | ||
if (!stats.hasAccess(mode)) { | ||
throw new ErrnoError(Errno.EACCES); | ||
@@ -988,0 +987,0 @@ } |
@@ -13,3 +13,2 @@ import { Buffer } from 'buffer'; | ||
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { credentials } from '../credentials.js'; | ||
import { emitChange } from './watchers.js'; | ||
@@ -27,3 +26,3 @@ | ||
const newMount = resolveMount(newPath); | ||
if (!statSync(dirname(oldPath)).hasAccess(constants.W_OK, credentials)) { | ||
if (!statSync(dirname(oldPath)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
@@ -78,3 +77,3 @@ } | ||
const stats = fs.statSync(resolved); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
if (!stats.hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
@@ -132,3 +131,3 @@ } | ||
try { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
@@ -158,3 +157,3 @@ } | ||
const parentStats: Stats = fs.statSync(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK, credentials)) { | ||
if (!parentStats.hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
@@ -170,3 +169,3 @@ } | ||
if (!stats.hasAccess(mode, credentials) || !stats.hasAccess(flagToMode(flag), credentials)) { | ||
if (!stats.hasAccess(mode) || !stats.hasAccess(flagToMode(flag))) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
@@ -475,3 +474,3 @@ } | ||
try { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', resolved, 'rmdir'); | ||
@@ -506,3 +505,3 @@ } | ||
if (!options?.recursive) { | ||
if (!fs.statSync(dirname(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(dirname(resolved)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
@@ -519,3 +518,3 @@ } | ||
for (const dir of dirs) { | ||
if (!fs.statSync(dirname(dir)).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(dirname(dir)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
@@ -548,3 +547,3 @@ } | ||
let entries: string[]; | ||
if (!statSync(path).hasAccess(constants.R_OK, credentials)) { | ||
if (!statSync(path).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
@@ -591,7 +590,7 @@ } | ||
targetPath = normalizePath(targetPath); | ||
if (!statSync(dirname(targetPath)).hasAccess(constants.R_OK, credentials)) { | ||
if (!statSync(dirname(targetPath)).hasAccess(constants.R_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!statSync(dirname(linkPath)).hasAccess(constants.W_OK, credentials)) { | ||
if (!statSync(dirname(linkPath)).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
@@ -606,3 +605,3 @@ } | ||
try { | ||
if (!fs.statSync(path).hasAccess(constants.W_OK, credentials)) { | ||
if (!fs.statSync(path).hasAccess(constants.W_OK)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
@@ -768,3 +767,3 @@ } | ||
const stats = statSync(path); | ||
if (!stats.hasAccess(mode, credentials)) { | ||
if (!stats.hasAccess(mode)) { | ||
throw new ErrnoError(Errno.EACCES); | ||
@@ -771,0 +770,0 @@ } |
@@ -149,2 +149,14 @@ import type { FileReadResult } from 'node:fs/promises'; | ||
export abstract class File { | ||
constructor( | ||
/** | ||
* @internal | ||
* The file system that created the file | ||
*/ | ||
public fs: FileSystem, | ||
/** | ||
* The path to the file | ||
*/ | ||
public readonly path: string | ||
) {} | ||
/** | ||
@@ -156,7 +168,2 @@ * Get the current file position. | ||
/** | ||
* The path to the file | ||
*/ | ||
public abstract readonly path: string; | ||
/** | ||
* Asynchronous `stat`. | ||
@@ -360,8 +367,6 @@ */ | ||
* The file system that created the file. | ||
* @internal | ||
*/ | ||
protected fs: FS, | ||
/** | ||
* Path to the file | ||
*/ | ||
public readonly path: string, | ||
public fs: FS, | ||
path: string, | ||
public readonly flag: string, | ||
@@ -371,3 +376,3 @@ public readonly stats: Stats, | ||
) { | ||
super(); | ||
super(fs, path); | ||
@@ -374,0 +379,0 @@ /* |
import { ErrnoError } from '../error.js'; | ||
import type { File } from '../file.js'; | ||
import type { FileSystem } from '../filesystem.js'; | ||
import type { FileSystem, FileSystemMetadata } from '../filesystem.js'; | ||
import '../polyfills.js'; | ||
import type { Stats } from '../stats.js'; | ||
import type { Mixin } from './shared.js'; | ||
import type { Concrete } from '../utils.js'; | ||
@@ -16,6 +16,3 @@ export class MutexLock { | ||
public constructor( | ||
public readonly path: string, | ||
protected readonly previous?: MutexLock | ||
) {} | ||
public constructor(protected readonly previous?: MutexLock) {} | ||
@@ -38,217 +35,225 @@ public async done(): Promise<void> { | ||
/** | ||
* This serializes access to an underlying async filesystem. | ||
* For example, on an OverlayFS instance with an async lower | ||
* directory operations like rename and rmdir may involve multiple | ||
* requests involving both the upper and lower filesystems -- they | ||
* are not executed in a single atomic step. OverlayFS uses this | ||
* to avoid having to reason about the correctness of | ||
* multiple requests interleaving. | ||
* | ||
* Note: `@ts-expect-error 2513` is needed because `FS` is not properly detected as being concrete | ||
* | ||
* @todo Change `using _` to `using void` pending https://github.com/tc39/proposal-discard-binding | ||
* @internal | ||
* @hidden | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function Mutexed<T extends new (...args: any[]) => FileSystem>( | ||
FS: T | ||
): Mixin< | ||
T, | ||
{ | ||
lock(path: string, syscall: string): Promise<MutexLock>; | ||
lockSync(path: string): MutexLock; | ||
isLocked(path: string): boolean; | ||
export class __MutexedFS<T extends FileSystem> implements FileSystem { | ||
/** | ||
* @internal | ||
*/ | ||
public _fs!: T; | ||
public async ready(): Promise<void> { | ||
return await this._fs.ready(); | ||
} | ||
> { | ||
class MutexedFS extends FS { | ||
/** | ||
* The current locks | ||
*/ | ||
private locks: Map<string, MutexLock> = new Map(); | ||
/** | ||
* Adds a lock for a path | ||
*/ | ||
protected addLock(path: string): MutexLock { | ||
const previous = this.locks.get(path); | ||
const lock = new MutexLock(path, previous?.isLocked ? previous : undefined); | ||
this.locks.set(path, lock); | ||
return lock; | ||
} | ||
public metadata(): FileSystemMetadata { | ||
return this._fs.metadata(); | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, waits for it to be unlocked. | ||
* @internal | ||
*/ | ||
public async lock(path: string, syscall: string): Promise<MutexLock> { | ||
const previous = this.locks.get(path); | ||
const lock = this.addLock(path); | ||
const stack = new Error().stack; | ||
setTimeout(() => { | ||
if (lock.isLocked) { | ||
const error = ErrnoError.With('EDEADLK', path, syscall); | ||
error.stack += stack?.slice('Error'.length); | ||
throw error; | ||
} | ||
}, 5000); | ||
await previous?.done(); | ||
return lock; | ||
} | ||
/** | ||
* The current locks | ||
*/ | ||
private currentLock?: MutexLock; | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, an error will be thrown | ||
* @internal | ||
*/ | ||
public lockSync(path: string): MutexLock { | ||
if (this.locks.has(path)) { | ||
// Non-null assertion: we already checked locks has path | ||
throw ErrnoError.With('EBUSY', path, 'lock'); | ||
/** | ||
* Adds a lock for a path | ||
*/ | ||
protected addLock(): MutexLock { | ||
const lock = new MutexLock(this.currentLock); | ||
this.currentLock = lock; | ||
return lock; | ||
} | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, waits for it to be unlocked. | ||
* @internal | ||
*/ | ||
public async lock(path: string, syscall: string): Promise<MutexLock> { | ||
const previous = this.currentLock; | ||
const lock = this.addLock(); | ||
const stack = new Error().stack; | ||
setTimeout(() => { | ||
if (lock.isLocked) { | ||
const error = ErrnoError.With('EDEADLK', path, syscall); | ||
error.stack += stack?.slice('Error'.length); | ||
throw error; | ||
} | ||
}, 5000); | ||
await previous?.done(); | ||
return lock; | ||
} | ||
return this.addLock(path); | ||
/** | ||
* Locks `path` asynchronously. | ||
* If the path is currently locked, an error will be thrown | ||
* @internal | ||
*/ | ||
public lockSync(path: string, syscall: string): MutexLock { | ||
if (this.currentLock) { | ||
throw ErrnoError.With('EBUSY', path, syscall); | ||
} | ||
/** | ||
* Whether `path` is locked | ||
* @internal | ||
*/ | ||
public isLocked(path: string): boolean { | ||
return !!this.locks.get(path)?.isLocked; | ||
} | ||
return this.addLock(); | ||
} | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
public async rename(oldPath: string, newPath: string): Promise<void> { | ||
using _ = await this.lock(oldPath, 'rename'); | ||
// @ts-expect-error 2513 | ||
await super.rename(oldPath, newPath); | ||
} | ||
/** | ||
* Whether `path` is locked | ||
* @internal | ||
*/ | ||
public get isLocked(): boolean { | ||
return !!this.currentLock?.isLocked; | ||
} | ||
public renameSync(oldPath: string, newPath: string): void { | ||
using _ = this.lockSync(oldPath); | ||
// @ts-expect-error 2513 | ||
return super.renameSync(oldPath, newPath); | ||
} | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
public async rename(oldPath: string, newPath: string): Promise<void> { | ||
using _ = await this.lock(oldPath, 'rename'); | ||
await this._fs.rename(oldPath, newPath); | ||
} | ||
public async stat(path: string): Promise<Stats> { | ||
using _ = await this.lock(path, 'stat'); | ||
// @ts-expect-error 2513 | ||
return await super.stat(path); | ||
} | ||
public renameSync(oldPath: string, newPath: string): void { | ||
using _ = this.lockSync(oldPath, 'rename'); | ||
return this._fs.renameSync(oldPath, newPath); | ||
} | ||
public statSync(path: string): Stats { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.statSync(path); | ||
} | ||
public async stat(path: string): Promise<Stats> { | ||
using _ = await this.lock(path, 'stat'); | ||
return await this._fs.stat(path); | ||
} | ||
public async openFile(path: string, flag: string): Promise<File> { | ||
using _ = await this.lock(path, 'openFile'); | ||
// @ts-expect-error 2513 | ||
return await super.openFile(path, flag); | ||
} | ||
public statSync(path: string): Stats { | ||
using _ = this.lockSync(path, 'stat'); | ||
return this._fs.statSync(path); | ||
} | ||
public openFileSync(path: string, flag: string): File { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.openFileSync(path, flag); | ||
} | ||
public async openFile(path: string, flag: string): Promise<File> { | ||
using _ = await this.lock(path, 'openFile'); | ||
const file = await this._fs.openFile(path, flag); | ||
file.fs = this; | ||
return file; | ||
} | ||
public async createFile(path: string, flag: string, mode: number): Promise<File> { | ||
using _ = await this.lock(path, 'createFile'); | ||
// @ts-expect-error 2513 | ||
return await super.createFile(path, flag, mode); | ||
} | ||
public openFileSync(path: string, flag: string): File { | ||
using _ = this.lockSync(path, 'openFile'); | ||
const file = this._fs.openFileSync(path, flag); | ||
file.fs = this; | ||
return file; | ||
} | ||
public createFileSync(path: string, flag: string, mode: number): File { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.createFileSync(path, flag, mode); | ||
} | ||
public async createFile(path: string, flag: string, mode: number): Promise<File> { | ||
using _ = await this.lock(path, 'createFile'); | ||
const file = await this._fs.createFile(path, flag, mode); | ||
file.fs = this; | ||
return file; | ||
} | ||
public async unlink(path: string): Promise<void> { | ||
using _ = await this.lock(path, 'unlink'); | ||
// @ts-expect-error 2513 | ||
await super.unlink(path); | ||
} | ||
public createFileSync(path: string, flag: string, mode: number): File { | ||
using _ = this.lockSync(path, 'createFile'); | ||
const file = this._fs.createFileSync(path, flag, mode); | ||
file.fs = this; | ||
return file; | ||
} | ||
public unlinkSync(path: string): void { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.unlinkSync(path); | ||
} | ||
public async unlink(path: string): Promise<void> { | ||
using _ = await this.lock(path, 'unlink'); | ||
await this._fs.unlink(path); | ||
} | ||
public async rmdir(path: string): Promise<void> { | ||
using _ = await this.lock(path, 'rmdir'); | ||
// @ts-expect-error 2513 | ||
await super.rmdir(path); | ||
} | ||
public unlinkSync(path: string): void { | ||
using _ = this.lockSync(path, 'unlink'); | ||
return this._fs.unlinkSync(path); | ||
} | ||
public rmdirSync(path: string): void { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.rmdirSync(path); | ||
} | ||
public async rmdir(path: string): Promise<void> { | ||
using _ = await this.lock(path, 'rmdir'); | ||
await this._fs.rmdir(path); | ||
} | ||
public async mkdir(path: string, mode: number): Promise<void> { | ||
using _ = await this.lock(path, 'mkdir'); | ||
// @ts-expect-error 2513 | ||
await super.mkdir(path, mode); | ||
} | ||
public rmdirSync(path: string): void { | ||
using _ = this.lockSync(path, 'rmdir'); | ||
return this._fs.rmdirSync(path); | ||
} | ||
public mkdirSync(path: string, mode: number): void { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.mkdirSync(path, mode); | ||
} | ||
public async mkdir(path: string, mode: number): Promise<void> { | ||
using _ = await this.lock(path, 'mkdir'); | ||
await this._fs.mkdir(path, mode); | ||
} | ||
public async readdir(path: string): Promise<string[]> { | ||
using _ = await this.lock(path, 'readdir'); | ||
// @ts-expect-error 2513 | ||
return await super.readdir(path); | ||
} | ||
public mkdirSync(path: string, mode: number): void { | ||
using _ = this.lockSync(path, 'mkdir'); | ||
return this._fs.mkdirSync(path, mode); | ||
} | ||
public readdirSync(path: string): string[] { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.readdirSync(path); | ||
} | ||
public async readdir(path: string): Promise<string[]> { | ||
using _ = await this.lock(path, 'readdir'); | ||
return await this._fs.readdir(path); | ||
} | ||
public async exists(path: string): Promise<boolean> { | ||
using _ = await this.lock(path, 'exists'); | ||
return await super.exists(path); | ||
} | ||
public readdirSync(path: string): string[] { | ||
using _ = this.lockSync(path, 'readdir'); | ||
return this._fs.readdirSync(path); | ||
} | ||
public existsSync(path: string): boolean { | ||
using _ = this.lockSync(path); | ||
return super.existsSync(path); | ||
} | ||
public async exists(path: string): Promise<boolean> { | ||
using _ = await this.lock(path, 'exists'); | ||
return await this._fs.exists(path); | ||
} | ||
public async link(srcpath: string, dstpath: string): Promise<void> { | ||
using _ = await this.lock(srcpath, 'link'); | ||
// @ts-expect-error 2513 | ||
await super.link(srcpath, dstpath); | ||
} | ||
public existsSync(path: string): boolean { | ||
using _ = this.lockSync(path, 'exists'); | ||
return this._fs.existsSync(path); | ||
} | ||
public linkSync(srcpath: string, dstpath: string): void { | ||
using _ = this.lockSync(srcpath); | ||
// @ts-expect-error 2513 | ||
return super.linkSync(srcpath, dstpath); | ||
} | ||
public async link(srcpath: string, dstpath: string): Promise<void> { | ||
using _ = await this.lock(srcpath, 'link'); | ||
await this._fs.link(srcpath, dstpath); | ||
} | ||
public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> { | ||
using _ = await this.lock(path, 'sync'); | ||
// @ts-expect-error 2513 | ||
await super.sync(path, data, stats); | ||
} | ||
public linkSync(srcpath: string, dstpath: string): void { | ||
using _ = this.lockSync(srcpath, 'link'); | ||
return this._fs.linkSync(srcpath, dstpath); | ||
} | ||
public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.syncSync(path, data, stats); | ||
public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> { | ||
using _ = await this.lock(path, 'sync'); | ||
await this._fs.sync(path, data, stats); | ||
} | ||
public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void { | ||
using _ = this.lockSync(path, 'sync'); | ||
return this._fs.syncSync(path, data, stats); | ||
} | ||
/* eslint-enable @typescript-eslint/no-unused-vars */ | ||
} | ||
/** | ||
* This serializes access to an underlying async filesystem. | ||
* For example, on an OverlayFS instance with an async lower | ||
* directory operations like rename and rmdir may involve multiple | ||
* requests involving both the upper and lower filesystems -- they | ||
* are not executed in a single atomic step. OverlayFS uses this | ||
* to avoid having to reason about the correctness of | ||
* multiple requests interleaving. | ||
* | ||
* Note: | ||
* Instead of extending the passed class, `MutexedFS` stores it internally. | ||
* This is to avoid a deadlock caused when a mathod calls another one | ||
* The problem is discussed extensivly in [#78](https://github.com/zen-fs/core/issues/78) | ||
* Instead of extending `FileSystem`, | ||
* `MutexedFS` implements it in order to make sure all of the methods are passed through | ||
* | ||
* @todo Change `using _` to `using void` pending https://github.com/tc39/proposal-discard-binding | ||
* @internal | ||
*/ | ||
export function Mutexed<const T extends Concrete<typeof FileSystem>>( | ||
FS: T | ||
): typeof __MutexedFS<InstanceType<T>> & { | ||
new (...args: ConstructorParameters<T>): __MutexedFS<InstanceType<T>>; | ||
} { | ||
class MutexedFS extends __MutexedFS<InstanceType<T>> { | ||
public constructor(...args: ConstructorParameters<T>) { | ||
super(); | ||
this._fs = new FS(...args) as InstanceType<T>; | ||
} | ||
/* eslint-enable @typescript-eslint/no-unused-vars */ | ||
} | ||
return MutexedFS; | ||
} |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
/* | ||
@@ -12,3 +13,2 @@ Code shared by various mixins | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export type Mixin<TBase extends typeof FileSystem, TMixin> = (abstract new (...args: any[]) => TMixin) & TBase; | ||
@@ -20,3 +20,4 @@ | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export type _AsyncFSMethods = ExtractProperties<FileSystem, (...args: any[]) => Promise<unknown>>; | ||
export type ConcreteFS = ExtractProperties<FileSystem, any>; |
import type * as Node from 'fs'; | ||
import type { Credentials } from './credentials.js'; | ||
import { credentials, type Credentials } from './credentials.js'; | ||
import { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRWXG, S_IRWXO, S_IRWXU } from './emulation/constants.js'; | ||
@@ -236,8 +236,7 @@ import { size_max } from './inode.js'; | ||
* @param mode The requested access, combination of W_OK, R_OK, and X_OK | ||
* @param cred The requesting credentials | ||
* @returns True if the request has access, false if the request does not | ||
* @internal | ||
*/ | ||
public hasAccess(mode: number, cred: Credentials): boolean { | ||
if (cred.euid === 0 || cred.egid === 0) { | ||
public hasAccess(mode: number): boolean { | ||
if (credentials.euid === 0 || credentials.egid === 0) { | ||
//Running as root | ||
@@ -248,3 +247,3 @@ return true; | ||
// Mask for | ||
const adjusted = (cred.uid == this.uid ? S_IRWXU : 0) | (cred.gid == this.gid ? S_IRWXG : 0) | S_IRWXO; | ||
const adjusted = (credentials.uid == this.uid ? S_IRWXU : 0) | (credentials.gid == this.gid ? S_IRWXG : 0) | S_IRWXO; | ||
return (mode & this.mode & adjusted) == mode; | ||
@@ -251,0 +250,0 @@ } |
/* eslint-disable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return */ | ||
import type * as fs from 'node:fs'; | ||
import type { OptionalTuple } from 'utilium'; | ||
import type { ClassLike, OptionalTuple } from 'utilium'; | ||
import { dirname, resolve, type AbsolutePath } from './emulation/path.js'; | ||
@@ -265,1 +265,3 @@ import { Errno, ErrnoError } from './error.js'; | ||
} | ||
export type Concrete<T extends ClassLike> = Pick<T, keyof T> & (new (...args: any[]) => InstanceType<T>); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
1964829
22180