@zenfs/core
Advanced tools
Comparing version 0.17.1 to 0.18.0
@@ -87,6 +87,5 @@ import type { RequiredKeys } from 'utilium'; | ||
* | ||
* Individual options can recursively contain BackendConfig objects for | ||
* option values that require file systems. | ||
* Individual options can recursively contain BackendConfiguration objects for values that require file systems. | ||
* | ||
* The option object for each file system corresponds to that file system's option object passed to its `Create()` method. | ||
* The configuration for each file system corresponds to that file system's option object passed to its `create()` method. | ||
*/ | ||
@@ -93,0 +92,0 @@ export type BackendConfiguration<T extends Backend> = OptionsOf<T> & Partial<SharedConfig> & { |
@@ -83,6 +83,6 @@ import { Errno, ErrnoError } from '../error.js'; | ||
if (!stats) { | ||
throw ErrnoError.With('ENOENT', path, 'preloadFile'); | ||
throw ErrnoError.With('ENOENT', path, 'preload'); | ||
} | ||
if (!stats.isFile()) { | ||
throw ErrnoError.With('EISDIR', path, 'preloadFile'); | ||
throw ErrnoError.With('EISDIR', path, 'preload'); | ||
} | ||
@@ -89,0 +89,0 @@ stats.size = buffer.length; |
@@ -1,2 +0,1 @@ | ||
import type { Cred } from '../cred.js'; | ||
import { NoSyncFile } from '../file.js'; | ||
@@ -48,14 +47,14 @@ import { FileSystem } from '../filesystem.js'; | ||
metadata(): import("../filesystem.js").FileSystemMetadata; | ||
rename(oldPath: string, newPath: string, cred: Cred): Promise<void>; | ||
renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
createFile(path: string, flag: string, mode: number, cred: Cred): Promise<import("../file.js").File>; | ||
createFileSync(path: string, flag: string, mode: number, cred: Cred): import("../file.js").File; | ||
unlink(path: string, cred: Cred): Promise<void>; | ||
unlinkSync(path: string, cred: Cred): void; | ||
rmdir(path: string, cred: Cred): Promise<void>; | ||
rmdirSync(path: string, cred: Cred): void; | ||
mkdir(path: string, mode: number, cred: Cred): Promise<void>; | ||
mkdirSync(path: string, mode: number, cred: Cred): void; | ||
link(srcpath: string, dstpath: string, cred: Cred): Promise<void>; | ||
linkSync(srcpath: string, dstpath: string, cred: Cred): void; | ||
rename(oldPath: string, newPath: string): Promise<void>; | ||
renameSync(oldPath: string, newPath: string): void; | ||
createFile(path: string, flag: string, mode: number): Promise<import("../file.js").File>; | ||
createFileSync(path: string, flag: string, mode: number): import("../file.js").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; | ||
link(srcpath: string, dstpath: string): Promise<void>; | ||
linkSync(srcpath: string, dstpath: string): void; | ||
sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>; | ||
@@ -74,4 +73,4 @@ syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void; | ||
statSync(path: string): Stats; | ||
openFile(path: string, flag: string, cred: Cred): Promise<NoSyncFile<this>>; | ||
openFileSync(path: string, flag: string, cred: Cred): NoSyncFile<this>; | ||
openFile(path: string, flag: string): Promise<NoSyncFile<this>>; | ||
openFileSync(path: string, flag: string): NoSyncFile<this>; | ||
readdir(path: string): Promise<string[]>; | ||
@@ -78,0 +77,0 @@ readdirSync(path: string): string[]; |
@@ -5,3 +5,3 @@ /* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */ | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { NoSyncFile, flagToMode, isWriteable } from '../file.js'; | ||
import { NoSyncFile, isWriteable } from '../file.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
@@ -122,3 +122,3 @@ import { Readonly } from '../mixins/readonly.js'; | ||
} | ||
async openFile(path, flag, cred) { | ||
async openFile(path, flag) { | ||
if (isWriteable(flag)) { | ||
@@ -133,8 +133,5 @@ // You can't write to files on this file system. | ||
} | ||
if (!stats.hasAccess(flagToMode(flag), cred)) { | ||
throw ErrnoError.With('EACCES', path, 'openFile'); | ||
} | ||
return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : await this.getData(path, stats)); | ||
} | ||
openFileSync(path, flag, cred) { | ||
openFileSync(path, flag) { | ||
if (isWriteable(flag)) { | ||
@@ -149,5 +146,2 @@ // You can't write to files on this file system. | ||
} | ||
if (!stats.hasAccess(flagToMode(flag), cred)) { | ||
throw ErrnoError.With('EACCES', path, 'openFile'); | ||
} | ||
return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : this.getDataSync(path, stats)); | ||
@@ -154,0 +148,0 @@ } |
@@ -0,6 +1,5 @@ | ||
import type { File } from '../file.js'; | ||
import type { FileSystemMetadata } from '../filesystem.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
import type { File } from '../file.js'; | ||
import { Stats } from '../stats.js'; | ||
import type { Cred } from '../cred.js'; | ||
/** | ||
@@ -48,21 +47,21 @@ * Configuration options for OverlayFS instances. | ||
getDeletionLog(): string; | ||
restoreDeletionLog(log: string, cred: Cred): Promise<void>; | ||
rename(oldPath: string, newPath: string, cred: Cred): Promise<void>; | ||
renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
stat(path: string, cred: Cred): Promise<Stats>; | ||
statSync(path: string, cred: Cred): Stats; | ||
openFile(path: string, flag: string, cred: Cred): Promise<File>; | ||
openFileSync(path: string, flag: string, cred: Cred): File; | ||
createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>; | ||
createFileSync(path: string, flag: string, mode: number, cred: Cred): File; | ||
link(srcpath: string, dstpath: string, cred: Cred): Promise<void>; | ||
linkSync(srcpath: string, dstpath: string, cred: Cred): void; | ||
unlink(path: string, cred: Cred): Promise<void>; | ||
unlinkSync(path: string, cred: Cred): void; | ||
rmdir(path: string, cred: Cred): Promise<void>; | ||
rmdirSync(path: string, cred: Cred): void; | ||
mkdir(path: string, mode: number, cred: Cred): Promise<void>; | ||
mkdirSync(path: string, mode: number, cred: Cred): void; | ||
readdir(path: string, cred: Cred): Promise<string[]>; | ||
readdirSync(path: string, cred: Cred): string[]; | ||
restoreDeletionLog(log: string): Promise<void>; | ||
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; | ||
link(srcpath: string, dstpath: string): Promise<void>; | ||
linkSync(srcpath: string, dstpath: string): void; | ||
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[]; | ||
private deletePath; | ||
@@ -94,3 +93,3 @@ private updateLog; | ||
declare const OverlayFS_base: import("../mixins/shared.js").Mixin<typeof UnmutexedOverlayFS, { | ||
lock(path: string): Promise<import("../mixins/mutexed.js").MutexLock>; | ||
lock(path: string, syscall: string): Promise<import("../mixins/mutexed.js").MutexLock>; | ||
lockSync(path: string): import("../mixins/mutexed.js").MutexLock; | ||
@@ -97,0 +96,0 @@ isLocked(path: string): boolean; |
@@ -0,8 +1,7 @@ | ||
import { dirname } from '../emulation/path.js'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { PreloadFile, parseFlag } from '../file.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
import { ErrnoError, Errno } from '../error.js'; | ||
import { PreloadFile, parseFlag } from '../file.js'; | ||
import { Mutexed } from '../mixins/mutexed.js'; | ||
import { Stats } from '../stats.js'; | ||
import { Mutexed } from '../mixins/mutexed.js'; | ||
import { dirname } from '../emulation/path.js'; | ||
import { rootCred } from '../cred.js'; | ||
import { decode, encode } from '../utils.js'; | ||
@@ -51,6 +50,5 @@ /** | ||
async sync(path, data, stats) { | ||
const cred = stats.cred(0, 0); | ||
await this.createParentDirectories(path, cred); | ||
if (!(await this.writable.exists(path, cred))) { | ||
await this.writable.createFile(path, 'w', 0o644, cred); | ||
await this.createParentDirectories(path); | ||
if (!(await this.writable.exists(path))) { | ||
await this.writable.createFile(path, 'w', 0o644); | ||
} | ||
@@ -60,4 +58,3 @@ await this.writable.sync(path, data, stats); | ||
syncSync(path, data, stats) { | ||
const cred = stats.cred(0, 0); | ||
this.createParentDirectoriesSync(path, cred); | ||
this.createParentDirectoriesSync(path); | ||
this.writable.syncSync(path, data, stats); | ||
@@ -75,3 +72,3 @@ } | ||
try { | ||
const file = await this.writable.openFile(deletionLogPath, parseFlag('r'), rootCred); | ||
const file = await this.writable.openFile(deletionLogPath, parseFlag('r')); | ||
const { size } = await file.stat(); | ||
@@ -92,8 +89,8 @@ const { buffer } = await file.read(new Uint8Array(size)); | ||
} | ||
async restoreDeletionLog(log, cred) { | ||
async restoreDeletionLog(log) { | ||
this._deleteLog = log; | ||
this._reparseDeletionLog(); | ||
await this.updateLog('', cred); | ||
await this.updateLog(''); | ||
} | ||
async rename(oldPath, newPath, cred) { | ||
async rename(oldPath, newPath) { | ||
this.checkInitialized(); | ||
@@ -103,3 +100,3 @@ this.checkPath(oldPath); | ||
try { | ||
await this.writable.rename(oldPath, newPath, cred); | ||
await this.writable.rename(oldPath, newPath); | ||
} | ||
@@ -112,3 +109,3 @@ catch (e) { | ||
} | ||
renameSync(oldPath, newPath, cred) { | ||
renameSync(oldPath, newPath) { | ||
this.checkInitialized(); | ||
@@ -118,3 +115,3 @@ this.checkPath(oldPath); | ||
try { | ||
this.writable.renameSync(oldPath, newPath, cred); | ||
this.writable.renameSync(oldPath, newPath); | ||
} | ||
@@ -127,6 +124,6 @@ catch (e) { | ||
} | ||
async stat(path, cred) { | ||
async stat(path) { | ||
this.checkInitialized(); | ||
try { | ||
return await this.writable.stat(path, cred); | ||
return await this.writable.stat(path); | ||
} | ||
@@ -137,3 +134,3 @@ catch (e) { | ||
} | ||
const oldStat = new Stats(await this.readable.stat(path, cred)); | ||
const oldStat = new Stats(await this.readable.stat(path)); | ||
// Make the oldStat's mode writable. Preserve the topmost part of the mode, which specifies the type | ||
@@ -144,6 +141,6 @@ oldStat.mode |= 0o222; | ||
} | ||
statSync(path, cred) { | ||
statSync(path) { | ||
this.checkInitialized(); | ||
try { | ||
return this.writable.statSync(path, cred); | ||
return this.writable.statSync(path); | ||
} | ||
@@ -154,3 +151,3 @@ catch (e) { | ||
} | ||
const oldStat = new Stats(this.readable.statSync(path, cred)); | ||
const oldStat = new Stats(this.readable.statSync(path)); | ||
// Make the oldStat's mode writable. Preserve the topmost part of the mode, which specifies the type. | ||
@@ -161,8 +158,8 @@ oldStat.mode |= 0o222; | ||
} | ||
async openFile(path, flag, cred) { | ||
if (await this.writable.exists(path, cred)) { | ||
return this.writable.openFile(path, flag, cred); | ||
async openFile(path, flag) { | ||
if (await this.writable.exists(path)) { | ||
return this.writable.openFile(path, flag); | ||
} | ||
// Create an OverlayFile. | ||
const file = await this.readable.openFile(path, parseFlag('r'), cred); | ||
const file = await this.readable.openFile(path, parseFlag('r')); | ||
const stats = new Stats(await file.stat()); | ||
@@ -172,8 +169,8 @@ const { buffer } = await file.read(new Uint8Array(stats.size)); | ||
} | ||
openFileSync(path, flag, cred) { | ||
if (this.writable.existsSync(path, cred)) { | ||
return this.writable.openFileSync(path, flag, cred); | ||
openFileSync(path, flag) { | ||
if (this.writable.existsSync(path)) { | ||
return this.writable.openFileSync(path, flag); | ||
} | ||
// Create an OverlayFile. | ||
const file = this.readable.openFileSync(path, parseFlag('r'), cred); | ||
const file = this.readable.openFileSync(path, parseFlag('r')); | ||
const stats = new Stats(file.statSync()); | ||
@@ -184,105 +181,105 @@ const data = new Uint8Array(stats.size); | ||
} | ||
async createFile(path, flag, mode, cred) { | ||
async createFile(path, flag, mode) { | ||
this.checkInitialized(); | ||
await this.writable.createFile(path, flag, mode, cred); | ||
return this.openFile(path, flag, cred); | ||
await this.writable.createFile(path, flag, mode); | ||
return this.openFile(path, flag); | ||
} | ||
createFileSync(path, flag, mode, cred) { | ||
createFileSync(path, flag, mode) { | ||
this.checkInitialized(); | ||
this.writable.createFileSync(path, flag, mode, cred); | ||
return this.openFileSync(path, flag, cred); | ||
this.writable.createFileSync(path, flag, mode); | ||
return this.openFileSync(path, flag); | ||
} | ||
async link(srcpath, dstpath, cred) { | ||
async link(srcpath, dstpath) { | ||
this.checkInitialized(); | ||
await this.writable.link(srcpath, dstpath, cred); | ||
await this.writable.link(srcpath, dstpath); | ||
} | ||
linkSync(srcpath, dstpath, cred) { | ||
linkSync(srcpath, dstpath) { | ||
this.checkInitialized(); | ||
this.writable.linkSync(srcpath, dstpath, cred); | ||
this.writable.linkSync(srcpath, dstpath); | ||
} | ||
async unlink(path, cred) { | ||
async unlink(path) { | ||
this.checkInitialized(); | ||
this.checkPath(path); | ||
if (!(await this.exists(path, cred))) { | ||
if (!(await this.exists(path))) { | ||
throw ErrnoError.With('ENOENT', path, 'unlink'); | ||
} | ||
if (await this.writable.exists(path, cred)) { | ||
await this.writable.unlink(path, cred); | ||
if (await this.writable.exists(path)) { | ||
await this.writable.unlink(path); | ||
} | ||
// if it still exists add to the delete log | ||
if (await this.exists(path, cred)) { | ||
await this.deletePath(path, cred); | ||
if (await this.exists(path)) { | ||
await this.deletePath(path); | ||
} | ||
} | ||
unlinkSync(path, cred) { | ||
unlinkSync(path) { | ||
this.checkInitialized(); | ||
this.checkPath(path); | ||
if (!this.existsSync(path, cred)) { | ||
if (!this.existsSync(path)) { | ||
throw ErrnoError.With('ENOENT', path, 'unlink'); | ||
} | ||
if (this.writable.existsSync(path, cred)) { | ||
this.writable.unlinkSync(path, cred); | ||
if (this.writable.existsSync(path)) { | ||
this.writable.unlinkSync(path); | ||
} | ||
// if it still exists add to the delete log | ||
if (this.existsSync(path, cred)) { | ||
void this.deletePath(path, cred); | ||
if (this.existsSync(path)) { | ||
void this.deletePath(path); | ||
} | ||
} | ||
async rmdir(path, cred) { | ||
async rmdir(path) { | ||
this.checkInitialized(); | ||
if (!(await this.exists(path, cred))) { | ||
if (!(await this.exists(path))) { | ||
throw ErrnoError.With('ENOENT', path, 'rmdir'); | ||
} | ||
if (await this.writable.exists(path, cred)) { | ||
await this.writable.rmdir(path, cred); | ||
if (await this.writable.exists(path)) { | ||
await this.writable.rmdir(path); | ||
} | ||
if (await this.exists(path, cred)) { | ||
if (await this.exists(path)) { | ||
// Check if directory is empty. | ||
if ((await this.readdir(path, cred)).length > 0) { | ||
if ((await this.readdir(path)).length > 0) { | ||
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir'); | ||
} | ||
else { | ||
await this.deletePath(path, cred); | ||
await this.deletePath(path); | ||
} | ||
} | ||
} | ||
rmdirSync(path, cred) { | ||
rmdirSync(path) { | ||
this.checkInitialized(); | ||
if (!this.existsSync(path, cred)) { | ||
if (!this.existsSync(path)) { | ||
throw ErrnoError.With('ENOENT', path, 'rmdir'); | ||
} | ||
if (this.writable.existsSync(path, cred)) { | ||
this.writable.rmdirSync(path, cred); | ||
if (this.writable.existsSync(path)) { | ||
this.writable.rmdirSync(path); | ||
} | ||
if (this.existsSync(path, cred)) { | ||
if (this.existsSync(path)) { | ||
// Check if directory is empty. | ||
if (this.readdirSync(path, cred).length > 0) { | ||
if (this.readdirSync(path).length > 0) { | ||
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir'); | ||
} | ||
else { | ||
void this.deletePath(path, cred); | ||
void this.deletePath(path); | ||
} | ||
} | ||
} | ||
async mkdir(path, mode, cred) { | ||
async mkdir(path, mode) { | ||
this.checkInitialized(); | ||
if (await this.exists(path, cred)) { | ||
if (await this.exists(path)) { | ||
throw ErrnoError.With('EEXIST', path, 'mkdir'); | ||
} | ||
// The below will throw should any of the parent directories fail to exist on _writable. | ||
await this.createParentDirectories(path, cred); | ||
await this.writable.mkdir(path, mode, cred); | ||
await this.createParentDirectories(path); | ||
await this.writable.mkdir(path, mode); | ||
} | ||
mkdirSync(path, mode, cred) { | ||
mkdirSync(path, mode) { | ||
this.checkInitialized(); | ||
if (this.existsSync(path, cred)) { | ||
if (this.existsSync(path)) { | ||
throw ErrnoError.With('EEXIST', path, 'mkdir'); | ||
} | ||
// The below will throw should any of the parent directories fail to exist on _writable. | ||
this.createParentDirectoriesSync(path, cred); | ||
this.writable.mkdirSync(path, mode, cred); | ||
this.createParentDirectoriesSync(path); | ||
this.writable.mkdirSync(path, mode); | ||
} | ||
async readdir(path, cred) { | ||
async readdir(path) { | ||
this.checkInitialized(); | ||
const dirStats = await this.stat(path, cred); | ||
const dirStats = await this.stat(path); | ||
if (!dirStats.isDirectory()) { | ||
@@ -294,3 +291,3 @@ throw ErrnoError.With('ENOTDIR', path, 'readdir'); | ||
try { | ||
contents.push(...(await this.writable.readdir(path, cred))); | ||
contents.push(...(await this.writable.readdir(path))); | ||
} | ||
@@ -301,3 +298,3 @@ catch (e) { | ||
try { | ||
contents.push(...(await this.readable.readdir(path, cred)).filter((fPath) => !this._deletedFiles.has(`${path}/${fPath}`))); | ||
contents.push(...(await this.readable.readdir(path)).filter((fPath) => !this._deletedFiles.has(`${path}/${fPath}`))); | ||
} | ||
@@ -314,5 +311,5 @@ catch (e) { | ||
} | ||
readdirSync(path, cred) { | ||
readdirSync(path) { | ||
this.checkInitialized(); | ||
const dirStats = this.statSync(path, cred); | ||
const dirStats = this.statSync(path); | ||
if (!dirStats.isDirectory()) { | ||
@@ -324,3 +321,3 @@ throw ErrnoError.With('ENOTDIR', path, 'readdir'); | ||
try { | ||
contents = contents.concat(this.writable.readdirSync(path, cred)); | ||
contents = contents.concat(this.writable.readdirSync(path)); | ||
} | ||
@@ -331,3 +328,3 @@ catch (e) { | ||
try { | ||
contents = contents.concat(this.readable.readdirSync(path, cred).filter((fPath) => !this._deletedFiles.has(`${path}/${fPath}`))); | ||
contents = contents.concat(this.readable.readdirSync(path).filter((fPath) => !this._deletedFiles.has(`${path}/${fPath}`))); | ||
} | ||
@@ -344,7 +341,7 @@ catch (e) { | ||
} | ||
async deletePath(path, cred) { | ||
async deletePath(path) { | ||
this._deletedFiles.add(path); | ||
await this.updateLog(`d${path}\n`, cred); | ||
await this.updateLog(`d${path}\n`); | ||
} | ||
async updateLog(addition, cred) { | ||
async updateLog(addition) { | ||
this._deleteLog += addition; | ||
@@ -356,3 +353,3 @@ if (this._deleteLogUpdatePending) { | ||
this._deleteLogUpdatePending = true; | ||
const log = await this.writable.openFile(deletionLogPath, parseFlag('w'), cred); | ||
const log = await this.writable.openFile(deletionLogPath, parseFlag('w')); | ||
try { | ||
@@ -362,3 +359,3 @@ await log.write(encode(this._deleteLog)); | ||
this._deleteLogUpdateNeeded = false; | ||
await this.updateLog('', cred); | ||
await this.updateLog(''); | ||
} | ||
@@ -403,5 +400,5 @@ } | ||
*/ | ||
createParentDirectoriesSync(path, cred) { | ||
createParentDirectoriesSync(path) { | ||
let parent = dirname(path), toCreate = []; | ||
while (!this.writable.existsSync(parent, cred)) { | ||
while (!this.writable.existsSync(parent)) { | ||
toCreate.push(parent); | ||
@@ -412,8 +409,8 @@ parent = dirname(parent); | ||
for (const p of toCreate) { | ||
this.writable.mkdirSync(p, this.statSync(p, cred).mode, cred); | ||
this.writable.mkdirSync(p, this.statSync(p).mode); | ||
} | ||
} | ||
async createParentDirectories(path, cred) { | ||
async createParentDirectories(path) { | ||
let parent = dirname(path), toCreate = []; | ||
while (!(await this.writable.exists(parent, cred))) { | ||
while (!(await this.writable.exists(parent))) { | ||
toCreate.push(parent); | ||
@@ -424,4 +421,4 @@ parent = dirname(parent); | ||
for (const p of toCreate) { | ||
const stats = await this.stat(p, cred); | ||
await this.writable.mkdir(p, stats.mode, cred); | ||
const stats = await this.stat(p); | ||
await this.writable.mkdir(p, stats.mode); | ||
} | ||
@@ -434,18 +431,18 @@ } | ||
*/ | ||
operateOnWritable(path, cred) { | ||
if (!this.existsSync(path, cred)) { | ||
operateOnWritable(path) { | ||
if (!this.existsSync(path)) { | ||
throw ErrnoError.With('ENOENT', path, 'operateOnWriteable'); | ||
} | ||
if (!this.writable.existsSync(path, cred)) { | ||
if (!this.writable.existsSync(path)) { | ||
// File is on readable storage. Copy to writable storage before | ||
// changing its mode. | ||
this.copyToWritableSync(path, cred); | ||
this.copyToWritableSync(path); | ||
} | ||
} | ||
async operateOnWritableAsync(path, cred) { | ||
if (!(await this.exists(path, cred))) { | ||
async operateOnWritableAsync(path) { | ||
if (!(await this.exists(path))) { | ||
throw ErrnoError.With('ENOENT', path, 'operateOnWritable'); | ||
} | ||
if (!(await this.writable.exists(path, cred))) { | ||
return this.copyToWritable(path, cred); | ||
if (!(await this.writable.exists(path))) { | ||
return this.copyToWritable(path); | ||
} | ||
@@ -457,27 +454,27 @@ } | ||
*/ | ||
copyToWritableSync(path, cred) { | ||
const stats = this.statSync(path, cred); | ||
copyToWritableSync(path) { | ||
const stats = this.statSync(path); | ||
if (stats.isDirectory()) { | ||
this.writable.mkdirSync(path, stats.mode, cred); | ||
this.writable.mkdirSync(path, stats.mode); | ||
return; | ||
} | ||
const data = new Uint8Array(stats.size); | ||
const readable = this.readable.openFileSync(path, parseFlag('r'), cred); | ||
const readable = this.readable.openFileSync(path, parseFlag('r')); | ||
readable.readSync(data); | ||
readable.closeSync(); | ||
const writable = this.writable.openFileSync(path, parseFlag('w'), cred); | ||
const writable = this.writable.openFileSync(path, parseFlag('w')); | ||
writable.writeSync(data); | ||
writable.closeSync(); | ||
} | ||
async copyToWritable(path, cred) { | ||
const stats = await this.stat(path, cred); | ||
async copyToWritable(path) { | ||
const stats = await this.stat(path); | ||
if (stats.isDirectory()) { | ||
await this.writable.mkdir(path, stats.mode, cred); | ||
await this.writable.mkdir(path, stats.mode); | ||
return; | ||
} | ||
const data = new Uint8Array(stats.size); | ||
const readable = await this.readable.openFile(path, parseFlag('r'), cred); | ||
const readable = await this.readable.openFile(path, parseFlag('r')); | ||
await readable.read(data); | ||
await readable.close(); | ||
const writable = await this.writable.openFile(path, parseFlag('w'), cred); | ||
const writable = await this.writable.openFile(path, parseFlag('w')); | ||
await writable.write(data); | ||
@@ -484,0 +481,0 @@ await writable.close(); |
@@ -5,3 +5,3 @@ /// <reference types="node" resolution-mode="require"/> | ||
import type { ExtractProperties } from 'utilium'; | ||
import type { Cred } from '../../cred.js'; | ||
import { type MountConfiguration } from '../../config.js'; | ||
import { File } from '../../file.js'; | ||
@@ -12,3 +12,2 @@ import { FileSystem, type FileSystemMetadata } from '../../filesystem.js'; | ||
import * as RPC from './rpc.js'; | ||
import { type MountConfiguration } from '../../config.js'; | ||
type FileMethods = Omit<ExtractProperties<File, (...args: any[]) => Promise<any>>, typeof Symbol.asyncDispose>; | ||
@@ -28,3 +27,3 @@ type FileMethod = keyof FileMethods; | ||
constructor(fs: PortFS, fd: number, path: string, position: number); | ||
rpc<const T extends FileMethod & string>(method: T, ...args: Parameters<FileMethods[T]>): Promise<Awaited<ReturnType<FileMethods[T]>>>; | ||
rpc<const T extends FileMethod>(method: T, ...args: Parameters<FileMethods[T]>): Promise<Awaited<ReturnType<FileMethods[T]>>>; | ||
protected _throwNoSync(syscall: string): never; | ||
@@ -63,11 +62,11 @@ stat(): Promise<Stats>; | ||
ready(): Promise<void>; | ||
renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
statSync(path: string, cred: Cred): Stats; | ||
createFileSync(path: string, flag: string, mode: number, cred: Cred): File; | ||
openFileSync(path: string, flag: string, cred: Cred): File; | ||
unlinkSync(path: string, cred: Cred): void; | ||
rmdirSync(path: string, cred: Cred): void; | ||
mkdirSync(path: string, mode: number, cred: Cred): void; | ||
readdirSync(path: string, cred: Cred): string[]; | ||
linkSync(srcpath: string, dstpath: string, cred: Cred): void; | ||
renameSync(oldPath: string, newPath: string): void; | ||
statSync(path: string): Stats; | ||
createFileSync(path: string, flag: string, mode: number): File; | ||
openFileSync(path: string, flag: string): File; | ||
unlinkSync(path: string): void; | ||
rmdirSync(path: string): void; | ||
mkdirSync(path: string, mode: number): void; | ||
readdirSync(path: string): string[]; | ||
linkSync(srcpath: string, dstpath: string): void; | ||
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void; | ||
@@ -96,13 +95,13 @@ }>; | ||
ready(): Promise<void>; | ||
rename(oldPath: string, newPath: string, cred: Cred): Promise<void>; | ||
stat(path: string, cred: Cred): Promise<Stats>; | ||
rename(oldPath: string, newPath: string): Promise<void>; | ||
stat(path: string): Promise<Stats>; | ||
sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>; | ||
openFile(path: string, flag: string, cred: Cred): Promise<File>; | ||
createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>; | ||
unlink(path: string, cred: Cred): Promise<void>; | ||
rmdir(path: string, cred: Cred): Promise<void>; | ||
mkdir(path: string, mode: number, cred: Cred): Promise<void>; | ||
readdir(path: string, cred: Cred): Promise<string[]>; | ||
exists(path: string, cred: Cred): Promise<boolean>; | ||
link(srcpath: string, dstpath: string, cred: Cred): Promise<void>; | ||
openFile(path: string, flag: string): Promise<File>; | ||
createFile(path: string, flag: string, mode: number): Promise<File>; | ||
unlink(path: string): Promise<void>; | ||
rmdir(path: string): Promise<void>; | ||
mkdir(path: string, mode: number): Promise<void>; | ||
readdir(path: string): Promise<string[]>; | ||
exists(path: string): Promise<boolean>; | ||
link(srcpath: string, dstpath: string): Promise<void>; | ||
} | ||
@@ -109,0 +108,0 @@ /** |
@@ -0,1 +1,2 @@ | ||
import { resolveMountConfig } from '../../config.js'; | ||
import { Errno, ErrnoError } from '../../error.js'; | ||
@@ -8,3 +9,2 @@ import { File } from '../../file.js'; | ||
import * as RPC from './rpc.js'; | ||
import { resolveMountConfig } from '../../config.js'; | ||
export class PortFile extends File { | ||
@@ -29,4 +29,4 @@ constructor(fs, fd, path, position) { | ||
} | ||
stat() { | ||
return this.rpc('stat'); | ||
async stat() { | ||
return new Stats(await this.rpc('stat')); | ||
} | ||
@@ -130,7 +130,7 @@ statSync() { | ||
} | ||
rename(oldPath, newPath, cred) { | ||
return this.rpc('rename', oldPath, newPath, cred); | ||
rename(oldPath, newPath) { | ||
return this.rpc('rename', oldPath, newPath); | ||
} | ||
async stat(path, cred) { | ||
return new Stats(await this.rpc('stat', path, cred)); | ||
async stat(path) { | ||
return new Stats(await this.rpc('stat', path)); | ||
} | ||
@@ -140,25 +140,25 @@ sync(path, data, stats) { | ||
} | ||
openFile(path, flag, cred) { | ||
return this.rpc('openFile', path, flag, cred); | ||
openFile(path, flag) { | ||
return this.rpc('openFile', path, flag); | ||
} | ||
createFile(path, flag, mode, cred) { | ||
return this.rpc('createFile', path, flag, mode, cred); | ||
createFile(path, flag, mode) { | ||
return this.rpc('createFile', path, flag, mode); | ||
} | ||
unlink(path, cred) { | ||
return this.rpc('unlink', path, cred); | ||
unlink(path) { | ||
return this.rpc('unlink', path); | ||
} | ||
rmdir(path, cred) { | ||
return this.rpc('rmdir', path, cred); | ||
rmdir(path) { | ||
return this.rpc('rmdir', path); | ||
} | ||
mkdir(path, mode, cred) { | ||
return this.rpc('mkdir', path, mode, cred); | ||
mkdir(path, mode) { | ||
return this.rpc('mkdir', path, mode); | ||
} | ||
readdir(path, cred) { | ||
return this.rpc('readdir', path, cred); | ||
readdir(path) { | ||
return this.rpc('readdir', path); | ||
} | ||
exists(path, cred) { | ||
return this.rpc('exists', path, cred); | ||
exists(path) { | ||
return this.rpc('exists', path); | ||
} | ||
link(srcpath, dstpath, cred) { | ||
return this.rpc('link', srcpath, dstpath, cred); | ||
link(srcpath, dstpath) { | ||
return this.rpc('link', srcpath, dstpath); | ||
} | ||
@@ -165,0 +165,0 @@ } |
@@ -1,2 +0,1 @@ | ||
import type { Cred } from '../../cred.js'; | ||
import { PreloadFile } from '../../file.js'; | ||
@@ -34,18 +33,18 @@ import { FileSystem, type FileSystemMetadata } from '../../filesystem.js'; | ||
*/ | ||
rename(oldPath: string, newPath: string, cred: Cred): Promise<void>; | ||
renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
stat(path: string, cred: Cred): Promise<Stats>; | ||
statSync(path: string, cred: Cred): Stats; | ||
createFile(path: string, flag: string, mode: number, cred: Cred): Promise<PreloadFile<this>>; | ||
createFileSync(path: string, flag: string, mode: number, cred: Cred): PreloadFile<this>; | ||
openFile(path: string, flag: string, cred: Cred): Promise<PreloadFile<this>>; | ||
openFileSync(path: string, flag: string, cred: Cred): PreloadFile<this>; | ||
unlink(path: string, cred: Cred): Promise<void>; | ||
unlinkSync(path: string, cred: Cred): void; | ||
rmdir(path: string, cred: Cred): Promise<void>; | ||
rmdirSync(path: string, cred: Cred): void; | ||
mkdir(path: string, mode: number, cred: Cred): Promise<void>; | ||
mkdirSync(path: string, mode: number, cred: Cred): void; | ||
readdir(path: string, cred: Cred): Promise<string[]>; | ||
readdirSync(path: string, cred: Cred): string[]; | ||
rename(oldPath: string, newPath: string): Promise<void>; | ||
renameSync(oldPath: string, newPath: string): void; | ||
stat(path: string): Promise<Stats>; | ||
statSync(path: string): Stats; | ||
createFile(path: string, flag: string, mode: number): Promise<PreloadFile<this>>; | ||
createFileSync(path: string, flag: string, mode: number): PreloadFile<this>; | ||
openFile(path: string, flag: string): Promise<PreloadFile<this>>; | ||
openFileSync(path: string, flag: string): PreloadFile<this>; | ||
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[]; | ||
/** | ||
@@ -61,4 +60,4 @@ * Updated the inode and data node at the given path | ||
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void; | ||
link(existing: string, newpath: string, cred: Cred): Promise<void>; | ||
linkSync(existing: string, newpath: string, cred: Cred): void; | ||
link(target: string, link: string): Promise<void>; | ||
linkSync(target: string, link: string): void; | ||
/** | ||
@@ -156,3 +155,3 @@ * Checks if the root directory exists. Creates it if it doesn't. | ||
*/ | ||
protected commitNewSync(path: string, type: FileType, mode: number, cred: Cred, data?: Uint8Array): Inode; | ||
protected commitNewSync(path: string, type: FileType, mode: number, data?: Uint8Array): Inode; | ||
/** | ||
@@ -171,3 +170,3 @@ * Remove all traces of the given path from the file system. | ||
*/ | ||
protected removeSync(path: string, isDir: boolean, cred: Cred): void; | ||
protected removeSync(path: string, isDir: boolean): void; | ||
} |
@@ -46,6 +46,7 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
}); | ||
import { R_OK, S_IFDIR, S_IFREG, W_OK } from '../../emulation/constants.js'; | ||
import { credentials } from '../../credentials.js'; | ||
import { S_IFDIR, S_IFREG } from '../../emulation/constants.js'; | ||
import { basename, dirname, join, resolve } from '../../emulation/path.js'; | ||
import { Errno, ErrnoError } from '../../error.js'; | ||
import { PreloadFile, flagToMode } from '../../file.js'; | ||
import { PreloadFile } from '../../file.js'; | ||
import { FileSystem } from '../../filesystem.js'; | ||
@@ -103,3 +104,3 @@ import { Inode, randomIno, rootIno } from '../../inode.js'; | ||
*/ | ||
async rename(oldPath, newPath, cred) { | ||
async rename(oldPath, newPath) { | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
@@ -111,5 +112,2 @@ try { | ||
oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent); | ||
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
} | ||
if (!oldDirList[oldName]) { | ||
@@ -140,12 +138,9 @@ throw ErrnoError.With('ENOENT', oldPath, 'rename'); | ||
if (newDirList[newName]) { | ||
// If it's a file, delete it. | ||
// If it's a file, delete it, if it's a directory, throw a permissions error. | ||
const newNameNode = await this.getINode(tx, newDirList[newName], newPath); | ||
if (newNameNode.toStats().isFile()) { | ||
await tx.remove(newNameNode.ino); | ||
await tx.remove(newDirList[newName]); | ||
} | ||
else { | ||
// If it's a directory, throw a permissions error. | ||
if (!newNameNode.toStats().isFile()) { | ||
throw ErrnoError.With('EPERM', newPath, 'rename'); | ||
} | ||
await tx.remove(newNameNode.ino); | ||
await tx.remove(newDirList[newName]); | ||
} | ||
@@ -168,3 +163,3 @@ newDirList[newName] = nodeId; | ||
} | ||
renameSync(oldPath, newPath, cred) { | ||
renameSync(oldPath, newPath) { | ||
const env_2 = { stack: [], error: void 0, hasError: false }; | ||
@@ -176,5 +171,2 @@ try { | ||
oldDirNode = this.findINodeSync(tx, oldParent), oldDirList = this.getDirListingSync(tx, oldDirNode, oldParent); | ||
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
} | ||
if (!oldDirList[oldName]) { | ||
@@ -205,12 +197,9 @@ throw ErrnoError.With('ENOENT', oldPath, 'rename'); | ||
if (newDirList[newName]) { | ||
// If it's a file, delete it. | ||
// If it's a file, delete it, if it's a directory, throw a permissions error. | ||
const newNameNode = this.getINodeSync(tx, newDirList[newName], newPath); | ||
if (newNameNode.toStats().isFile()) { | ||
tx.removeSync(newNameNode.ino); | ||
tx.removeSync(newDirList[newName]); | ||
} | ||
else { | ||
// If it's a directory, throw a permissions error. | ||
if (!newNameNode.toStats().isFile()) { | ||
throw ErrnoError.With('EPERM', newPath, 'rename'); | ||
} | ||
tx.removeSync(newNameNode.ino); | ||
tx.removeSync(newDirList[newName]); | ||
} | ||
@@ -231,3 +220,3 @@ newDirList[newName] = ino; | ||
} | ||
async stat(path, cred) { | ||
async stat(path) { | ||
const env_3 = { stack: [], error: void 0, hasError: false }; | ||
@@ -240,7 +229,3 @@ try { | ||
} | ||
const stats = inode.toStats(); | ||
if (!stats.hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
} | ||
return stats; | ||
return inode.toStats(); | ||
} | ||
@@ -257,3 +242,3 @@ catch (e_3) { | ||
} | ||
statSync(path, cred) { | ||
statSync(path) { | ||
const env_4 = { stack: [], error: void 0, hasError: false }; | ||
@@ -263,7 +248,3 @@ try { | ||
// Get the inode to the item, convert it into a Stats object. | ||
const stats = this.findINodeSync(tx, path).toStats(); | ||
if (!stats.hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
} | ||
return stats; | ||
return this.findINodeSync(tx, path).toStats(); | ||
} | ||
@@ -278,11 +259,11 @@ catch (e_4) { | ||
} | ||
async createFile(path, flag, mode, cred) { | ||
const node = await this.commitNew(path, S_IFREG, mode, cred, new Uint8Array(0)); | ||
async createFile(path, flag, mode) { | ||
const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(0)); | ||
return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array(0)); | ||
} | ||
createFileSync(path, flag, mode, cred) { | ||
this.commitNewSync(path, S_IFREG, mode, cred); | ||
return this.openFileSync(path, flag, cred); | ||
createFileSync(path, flag, mode) { | ||
this.commitNewSync(path, S_IFREG, mode); | ||
return this.openFileSync(path, flag); | ||
} | ||
async openFile(path, flag, cred) { | ||
async openFile(path, flag) { | ||
const env_5 = { stack: [], error: void 0, hasError: false }; | ||
@@ -292,5 +273,2 @@ try { | ||
const node = await this.findINode(tx, path), data = await tx.get(node.ino); | ||
if (!node.toStats().hasAccess(flagToMode(flag), cred)) { | ||
throw ErrnoError.With('EACCES', path, 'openFile'); | ||
} | ||
if (!data) { | ||
@@ -311,3 +289,3 @@ throw ErrnoError.With('ENOENT', path, 'openFile'); | ||
} | ||
openFileSync(path, flag, cred) { | ||
openFileSync(path, flag) { | ||
const env_6 = { stack: [], error: void 0, hasError: false }; | ||
@@ -317,5 +295,2 @@ try { | ||
const node = this.findINodeSync(tx, path), data = tx.getSync(node.ino); | ||
if (!node.toStats().hasAccess(flagToMode(flag), cred)) { | ||
throw ErrnoError.With('EACCES', path, 'openFile'); | ||
} | ||
if (!data) { | ||
@@ -334,32 +309,29 @@ throw ErrnoError.With('ENOENT', path, 'openFile'); | ||
} | ||
async unlink(path, cred) { | ||
return this.remove(path, false, cred); | ||
async unlink(path) { | ||
return this.remove(path, false); | ||
} | ||
unlinkSync(path, cred) { | ||
this.removeSync(path, false, cred); | ||
unlinkSync(path) { | ||
this.removeSync(path, false); | ||
} | ||
async rmdir(path, cred) { | ||
async rmdir(path) { | ||
// Check first if directory is empty. | ||
const list = await this.readdir(path, cred); | ||
if (list.length > 0) { | ||
if ((await this.readdir(path)).length) { | ||
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir'); | ||
} | ||
await this.remove(path, true, cred); | ||
await this.remove(path, true); | ||
} | ||
rmdirSync(path, cred) { | ||
rmdirSync(path) { | ||
// Check first if directory is empty. | ||
if (this.readdirSync(path, cred).length > 0) { | ||
if (this.readdirSync(path).length) { | ||
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir'); | ||
} | ||
else { | ||
this.removeSync(path, true, cred); | ||
} | ||
this.removeSync(path, true); | ||
} | ||
async mkdir(path, mode, cred) { | ||
await this.commitNew(path, S_IFDIR, mode, cred, encode('{}')); | ||
async mkdir(path, mode) { | ||
await this.commitNew(path, S_IFDIR, mode, encode('{}')); | ||
} | ||
mkdirSync(path, mode, cred) { | ||
this.commitNewSync(path, S_IFDIR, mode, cred, encode('{}')); | ||
mkdirSync(path, mode) { | ||
this.commitNewSync(path, S_IFDIR, mode, encode('{}')); | ||
} | ||
async readdir(path, cred) { | ||
async readdir(path) { | ||
const env_7 = { stack: [], error: void 0, hasError: false }; | ||
@@ -369,5 +341,2 @@ try { | ||
const node = await this.findINode(tx, path); | ||
if (!node.toStats().hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'readdur'); | ||
} | ||
return Object.keys(await this.getDirListing(tx, node, path)); | ||
@@ -385,3 +354,3 @@ } | ||
} | ||
readdirSync(path, cred) { | ||
readdirSync(path) { | ||
const env_8 = { stack: [], error: void 0, hasError: false }; | ||
@@ -391,5 +360,2 @@ try { | ||
const node = this.findINodeSync(tx, path); | ||
if (!node.toStats().hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
} | ||
return Object.keys(this.getDirListingSync(tx, node, path)); | ||
@@ -459,23 +425,13 @@ } | ||
} | ||
async link(existing, newpath, cred) { | ||
async link(target, link) { | ||
const env_11 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const tx = __addDisposableResource(env_11, this.store.transaction(), true); | ||
const existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir); | ||
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', existingDir, 'link'); | ||
} | ||
const newDir = dirname(newpath), newDirNode = await this.findINode(tx, newDir), newListing = await this.getDirListing(tx, newDirNode, newDir); | ||
if (!newDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', newDir, 'link'); | ||
} | ||
const ino = await this._findINode(tx, existingDir, basename(existing)); | ||
const node = await this.getINode(tx, ino, existing); | ||
if (!node.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', newpath, 'link'); | ||
} | ||
const newDir = dirname(link), newDirNode = await this.findINode(tx, newDir), listing = await this.getDirListing(tx, newDirNode, newDir); | ||
const ino = await this._findINode(tx, dirname(target), basename(target)); | ||
const node = await this.getINode(tx, ino, target); | ||
node.nlink++; | ||
newListing[basename(newpath)] = ino; | ||
listing[basename(link)] = ino; | ||
tx.setSync(ino, node.data); | ||
tx.setSync(newDirNode.ino, encodeDirListing(newListing)); | ||
tx.setSync(newDirNode.ino, encodeDirListing(listing)); | ||
tx.commitSync(); | ||
@@ -493,23 +449,13 @@ } | ||
} | ||
linkSync(existing, newpath, cred) { | ||
linkSync(target, link) { | ||
const env_12 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const tx = __addDisposableResource(env_12, this.store.transaction(), false); | ||
const existingDir = dirname(existing), existingDirNode = this.findINodeSync(tx, existingDir); | ||
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', existingDir, 'link'); | ||
} | ||
const newDir = dirname(newpath), newDirNode = this.findINodeSync(tx, newDir), newListing = this.getDirListingSync(tx, newDirNode, newDir); | ||
if (!newDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', newDir, 'link'); | ||
} | ||
const ino = this._findINodeSync(tx, existingDir, basename(existing)); | ||
const node = this.getINodeSync(tx, ino, existing); | ||
if (!node.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', newpath, 'link'); | ||
} | ||
const newDir = dirname(link), newDirNode = this.findINodeSync(tx, newDir), listing = this.getDirListingSync(tx, newDirNode, newDir); | ||
const ino = this._findINodeSync(tx, dirname(target), basename(target)); | ||
const node = this.getINodeSync(tx, ino, target); | ||
node.nlink++; | ||
newListing[basename(newpath)] = ino; | ||
listing[basename(link)] = ino; | ||
tx.setSync(ino, node.data); | ||
tx.setSync(newDirNode.ino, encodeDirListing(newListing)); | ||
tx.setSync(newDirNode.ino, encodeDirListing(listing)); | ||
tx.commitSync(); | ||
@@ -743,3 +689,3 @@ } | ||
*/ | ||
async commitNew(path, type, mode, cred, data) { | ||
async commitNew(path, type, mode, data) { | ||
const env_15 = { stack: [], error: void 0, hasError: false }; | ||
@@ -749,6 +695,2 @@ try { | ||
const parentPath = dirname(path), parent = await this.findINode(tx, parentPath); | ||
//Check that the creater has correct access | ||
if (!parent.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'commitNewFile'); | ||
} | ||
const fname = basename(path), listing = await this.getDirListing(tx, parent, parentPath); | ||
@@ -761,3 +703,3 @@ /* | ||
if (path === '/') { | ||
throw ErrnoError.With('EEXIST', path, 'commitNewFile'); | ||
throw ErrnoError.With('EEXIST', path, 'commitNew'); | ||
} | ||
@@ -767,3 +709,3 @@ // Check if file already exists. | ||
await tx.abort(); | ||
throw ErrnoError.With('EEXIST', path, 'commitNewFile'); | ||
throw ErrnoError.With('EEXIST', path, 'commitNew'); | ||
} | ||
@@ -774,4 +716,4 @@ // Commit data. | ||
inode.mode = mode | type; | ||
inode.uid = cred.uid; | ||
inode.gid = cred.gid; | ||
inode.uid = credentials.uid; | ||
inode.gid = credentials.gid; | ||
inode.size = data.length; | ||
@@ -803,3 +745,3 @@ // Update and commit parent directory listing. | ||
*/ | ||
commitNewSync(path, type, mode, cred, data = new Uint8Array()) { | ||
commitNewSync(path, type, mode, data = new Uint8Array()) { | ||
const env_16 = { stack: [], error: void 0, hasError: false }; | ||
@@ -809,6 +751,2 @@ try { | ||
const parentPath = dirname(path), parent = this.findINodeSync(tx, parentPath); | ||
//Check that the creater has correct access | ||
if (!parent.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'commitNewFile'); | ||
} | ||
const fname = basename(path), listing = this.getDirListingSync(tx, parent, parentPath); | ||
@@ -821,7 +759,7 @@ /* | ||
if (path === '/') { | ||
throw ErrnoError.With('EEXIST', path, 'commitNewFile'); | ||
throw ErrnoError.With('EEXIST', path, 'commitNew'); | ||
} | ||
// Check if file already exists. | ||
if (listing[fname]) { | ||
throw ErrnoError.With('EEXIST', path, 'commitNewFile'); | ||
throw ErrnoError.With('EEXIST', path, 'commitNew'); | ||
} | ||
@@ -833,4 +771,4 @@ // Commit data. | ||
node.mode = mode | type; | ||
node.uid = cred.uid; | ||
node.gid = cred.gid; | ||
node.uid = credentials.uid; | ||
node.gid = credentials.gid; | ||
// Update and commit parent directory listing. | ||
@@ -856,3 +794,3 @@ listing[fname] = this.addNewSync(tx, node.data, path); | ||
*/ | ||
async remove(path, isDir, cred) { | ||
async remove(path, isDir) { | ||
const env_17 = { stack: [], error: void 0, hasError: false }; | ||
@@ -863,3 +801,3 @@ try { | ||
if (!listing[fileName]) { | ||
throw ErrnoError.With('ENOENT', path, 'removeEntry'); | ||
throw ErrnoError.With('ENOENT', path, 'remove'); | ||
} | ||
@@ -869,12 +807,9 @@ const fileIno = listing[fileName]; | ||
const fileNode = await this.getINode(tx, fileIno, path); | ||
if (!fileNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'removeEntry'); | ||
} | ||
// Remove from directory listing of parent. | ||
delete listing[fileName]; | ||
if (!isDir && fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('EISDIR', path, 'removeEntry'); | ||
throw ErrnoError.With('EISDIR', path, 'remove'); | ||
} | ||
if (isDir && !fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'removeEntry'); | ||
throw ErrnoError.With('ENOTDIR', path, 'remove'); | ||
} | ||
@@ -906,3 +841,3 @@ await tx.set(parentNode.ino, encodeDirListing(listing)); | ||
*/ | ||
removeSync(path, isDir, cred) { | ||
removeSync(path, isDir) { | ||
const env_18 = { stack: [], error: void 0, hasError: false }; | ||
@@ -913,16 +848,13 @@ try { | ||
if (!fileIno) { | ||
throw ErrnoError.With('ENOENT', path, 'removeEntry'); | ||
throw ErrnoError.With('ENOENT', path, 'remove'); | ||
} | ||
// Get file inode. | ||
const fileNode = this.getINodeSync(tx, fileIno, path); | ||
if (!fileNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'removeEntry'); | ||
} | ||
// Remove from directory listing of parent. | ||
delete listing[fileName]; | ||
if (!isDir && fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('EISDIR', path, 'removeEntry'); | ||
throw ErrnoError.With('EISDIR', path, 'remove'); | ||
} | ||
if (isDir && !fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'removeEntry'); | ||
throw ErrnoError.With('ENOTDIR', path, 'remove'); | ||
} | ||
@@ -929,0 +861,0 @@ // Update directory listing. |
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js'; | ||
import { credentials } from './credentials.js'; | ||
import * as fs from './emulation/index.js'; | ||
import { setCred } from './emulation/shared.js'; | ||
import { Errno, ErrnoError } from './error.js'; | ||
@@ -66,3 +66,3 @@ import { FileSystem } from './filesystem.js'; | ||
const gid = 'gid' in config ? config.gid || 0 : 0; | ||
setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid }); | ||
Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid }); | ||
if (!config.mounts) { | ||
@@ -69,0 +69,0 @@ return; |
@@ -13,2 +13,3 @@ /// <reference types="node" resolution-mode="require"/> | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
import { FSWatcher } from './watchers.js'; | ||
/** | ||
@@ -362,3 +363,11 @@ * Asynchronous rename. No arguments other than a possible exception are given | ||
/** | ||
* @todo Implement | ||
* Watch for changes on a file. The callback listener will be called each time the file is accessed. | ||
* | ||
* The `options` argument may be omitted. If provided, it should be an object with a `persistent` boolean and an `interval` number specifying the polling interval in milliseconds. | ||
* | ||
* When a change is detected, the `listener` callback is called with the current and previous `Stats` objects. | ||
* | ||
* @param path The path to the file to watch. | ||
* @param options Optional options object specifying `persistent` and `interval`. | ||
* @param listener The callback listener to be called when the file changes. | ||
*/ | ||
@@ -371,9 +380,15 @@ export declare function watchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void): void; | ||
/** | ||
* @todo Implement | ||
* Stop watching for changes on a file. | ||
* | ||
* If the `listener` is specified, only that particular listener is removed. | ||
* If no `listener` is specified, all listeners are removed, and the file is no longer watched. | ||
* | ||
* @param path The path to the file to stop watching. | ||
* @param listener Optional listener to remove. | ||
*/ | ||
export declare function unwatchFile(path: fs.PathLike, listener?: (curr: Stats, prev: Stats) => void): void; | ||
export declare function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): fs.FSWatcher; | ||
export declare function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): FSWatcher; | ||
export declare function watch(path: fs.PathLike, options: { | ||
persistent?: boolean; | ||
}, listener?: (event: string, filename: string) => any): fs.FSWatcher; | ||
}, listener?: (event: string, filename: string) => any): FSWatcher; | ||
interface StreamOptions { | ||
@@ -380,0 +395,0 @@ flags?: string; |
import { Buffer } from 'buffer'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { BigIntStats } from '../stats.js'; | ||
import { normalizeMode } from '../utils.js'; | ||
import { normalizeMode, normalizePath } from '../utils.js'; | ||
import { R_OK } from './constants.js'; | ||
@@ -9,3 +9,3 @@ import * as promises from './promises.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
import { FSWatcher } from './watchers.js'; | ||
import { FSWatcher, StatWatcher } from './watchers.js'; | ||
const nop = () => { }; | ||
@@ -437,15 +437,62 @@ /** | ||
access; | ||
export function watchFile(path, optsListener, listener = nop) { | ||
throw ErrnoError.With('ENOSYS', path.toString(), 'watchFile'); | ||
const statWatchers = new Map(); | ||
export function watchFile(path, optsListener, listener) { | ||
const normalizedPath = normalizePath(path.toString()); | ||
const options = typeof optsListener != 'function' ? optsListener : {}; | ||
if (typeof optsListener === 'function') { | ||
listener = optsListener; | ||
} | ||
if (!listener) { | ||
throw new ErrnoError(Errno.EINVAL, 'No listener specified', path.toString(), 'watchFile'); | ||
} | ||
if (statWatchers.has(normalizedPath)) { | ||
const entry = statWatchers.get(normalizedPath); | ||
if (entry) { | ||
entry.listeners.add(listener); | ||
} | ||
return; | ||
} | ||
const watcher = new StatWatcher(normalizedPath, options); | ||
watcher.on('change', (curr, prev) => { | ||
const entry = statWatchers.get(normalizedPath); | ||
if (!entry) { | ||
return; | ||
} | ||
for (const listener of entry.listeners) { | ||
listener(curr, prev); | ||
} | ||
}); | ||
statWatchers.set(normalizedPath, { watcher, listeners: new Set() }); | ||
} | ||
watchFile; | ||
/** | ||
* @todo Implement | ||
* Stop watching for changes on a file. | ||
* | ||
* If the `listener` is specified, only that particular listener is removed. | ||
* If no `listener` is specified, all listeners are removed, and the file is no longer watched. | ||
* | ||
* @param path The path to the file to stop watching. | ||
* @param listener Optional listener to remove. | ||
*/ | ||
export function unwatchFile(path, listener = nop) { | ||
throw ErrnoError.With('ENOSYS', path.toString(), 'unwatchFile'); | ||
const normalizedPath = normalizePath(path.toString()); | ||
const entry = statWatchers.get(normalizedPath); | ||
if (entry) { | ||
if (listener && listener !== nop) { | ||
entry.listeners.delete(listener); | ||
} | ||
else { | ||
// If no listener is specified, remove all listeners | ||
entry.listeners.clear(); | ||
} | ||
if (entry.listeners.size === 0) { | ||
// No more listeners, stop the watcher | ||
entry.watcher.stop(); | ||
statWatchers.delete(normalizedPath); | ||
} | ||
} | ||
} | ||
unwatchFile; | ||
export function watch(path, options, listener) { | ||
const watcher = new FSWatcher(typeof options == 'object' ? options : {}); | ||
const watcher = new FSWatcher(normalizePath(path), typeof options == 'object' ? options : {}); | ||
listener = typeof options == 'function' ? options : listener; | ||
@@ -488,3 +535,3 @@ watcher.on('change', listener || nop); | ||
.then(() => callback(error)) | ||
.catch(callback); | ||
.catch(nop); | ||
}, | ||
@@ -491,0 +538,0 @@ }); |
/// <reference types="node" resolution-mode="require"/> | ||
import type { Dirent as _Dirent, Dir as _Dir } from 'fs'; | ||
import type { Dir as _Dir, Dirent as _Dirent } from 'fs'; | ||
import type { Stats } from '../stats.js'; | ||
import type { Callback } from '../utils.js'; | ||
import type { Stats } from '../stats.js'; | ||
export declare class Dirent implements _Dirent { | ||
@@ -26,7 +26,3 @@ path: string; | ||
protected checkClosed(): void; | ||
protected _entries: Dirent[]; | ||
/** | ||
* @internal | ||
*/ | ||
_loadEntries(): Promise<void>; | ||
protected _entries?: Dirent[]; | ||
constructor(path: string); | ||
@@ -58,2 +54,3 @@ /** | ||
readSync(): Dirent | null; | ||
next(): Promise<IteratorResult<Dirent>>; | ||
/** | ||
@@ -60,0 +57,0 @@ * Asynchronously iterates over the directory via `readdir(3)` until all entries have been read. |
@@ -0,5 +1,5 @@ | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { basename } from './path.js'; | ||
import { readdir } from './promises.js'; | ||
import { ErrnoError, Errno } from '../error.js'; | ||
import { readdirSync } from './sync.js'; | ||
import { basename } from './path.js'; | ||
export class Dirent { | ||
@@ -47,12 +47,5 @@ get name() { | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
async _loadEntries() { | ||
this._entries ?? (this._entries = await readdir(this.path, { withFileTypes: true })); | ||
} | ||
constructor(path) { | ||
this.path = path; | ||
this.closed = false; | ||
this._entries = []; | ||
} | ||
@@ -74,7 +67,8 @@ close(cb) { | ||
async _read() { | ||
await this._loadEntries(); | ||
this.checkClosed(); | ||
this._entries ?? (this._entries = await readdir(this.path, { withFileTypes: true })); | ||
if (!this._entries.length) { | ||
return null; | ||
} | ||
return this._entries.shift() || null; | ||
return this._entries.shift() ?? null; | ||
} | ||
@@ -93,2 +87,3 @@ read(cb) { | ||
readSync() { | ||
this.checkClosed(); | ||
this._entries ?? (this._entries = readdirSync(this.path, { withFileTypes: true })); | ||
@@ -98,4 +93,12 @@ if (!this._entries.length) { | ||
} | ||
return this._entries.shift() || null; | ||
return this._entries.shift() ?? null; | ||
} | ||
async next() { | ||
const value = await this._read(); | ||
if (value) { | ||
return { done: false, value }; | ||
} | ||
await this.close(); | ||
return { done: true, value: undefined }; | ||
} | ||
/** | ||
@@ -105,15 +108,4 @@ * Asynchronously iterates over the directory via `readdir(3)` until all entries have been read. | ||
[Symbol.asyncIterator]() { | ||
const _this = this; | ||
return { | ||
[Symbol.asyncIterator]: this[Symbol.asyncIterator], | ||
async next() { | ||
const value = await _this._read(); | ||
if (value != null) { | ||
return { done: false, value }; | ||
} | ||
await _this.close(); | ||
return { done: true, value: undefined }; | ||
}, | ||
}; | ||
return this; | ||
} | ||
} |
@@ -317,6 +317,6 @@ /// <reference types="node" resolution-mode="require"/> | ||
* `link`. | ||
* @param existing | ||
* @param newpath | ||
* @param targetPath | ||
* @param linkPath | ||
*/ | ||
export declare function link(existing: fs.PathLike, newpath: fs.PathLike): Promise<void>; | ||
export declare function link(targetPath: fs.PathLike, linkPath: fs.PathLike): Promise<void>; | ||
/** | ||
@@ -323,0 +323,0 @@ * `symlink`. |
@@ -49,3 +49,3 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js'; | ||
import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js'; | ||
import '../polyfills.js'; | ||
@@ -57,5 +57,6 @@ import { BigIntStats } from '../stats.js'; | ||
import { dirname, join, parse } from './path.js'; | ||
import { _statfs, cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { credentials } from '../credentials.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
import { FSWatcher } from './watchers.js'; | ||
import { FSWatcher, emitChange } from './watchers.js'; | ||
export * as constants from './constants.js'; | ||
@@ -71,4 +72,5 @@ export class FileHandle { | ||
*/ | ||
chown(uid, gid) { | ||
return this.file.chown(uid, gid); | ||
async chown(uid, gid) { | ||
await this.file.chown(uid, gid); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -79,3 +81,3 @@ /** | ||
*/ | ||
chmod(mode) { | ||
async chmod(mode) { | ||
const numMode = normalizeMode(mode, -1); | ||
@@ -85,3 +87,4 @@ if (numMode < 0) { | ||
} | ||
return this.file.chmod(numMode); | ||
await this.file.chmod(numMode); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -104,3 +107,3 @@ /** | ||
*/ | ||
truncate(len) { | ||
async truncate(len) { | ||
len || (len = 0); | ||
@@ -110,3 +113,4 @@ if (len < 0) { | ||
} | ||
return this.file.truncate(len); | ||
await this.file.truncate(len); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -118,4 +122,5 @@ /** | ||
*/ | ||
utimes(atime, mtime) { | ||
return this.file.utimes(normalizeTime(atime), normalizeTime(mtime)); | ||
async utimes(atime, mtime) { | ||
await this.file.utimes(normalizeTime(atime), normalizeTime(mtime)); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -143,2 +148,3 @@ /** | ||
await this.file.write(encodedData, 0, encodedData.length); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -219,2 +225,5 @@ /** | ||
const stats = await this.file.stat(); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', this.file.path, 'stat'); | ||
} | ||
return opts?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -241,2 +250,3 @@ } | ||
const bytesWritten = await this.file.write(buffer, offset, length, position); | ||
emitChange('change', this.file.path); | ||
return { buffer, bytesWritten }; | ||
@@ -266,2 +276,3 @@ } | ||
await this.file.write(encodedData, 0, encodedData.length, 0); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -361,5 +372,9 @@ /** | ||
const dst = resolveMount(newPath); | ||
if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
} | ||
try { | ||
if (src.mountPoint == dst.mountPoint) { | ||
await src.fs.rename(src.path, dst.path, cred); | ||
await src.fs.rename(src.path, dst.path); | ||
emitChange('rename', oldPath.toString()); | ||
return; | ||
@@ -369,2 +384,3 @@ } | ||
await unlink(oldPath); | ||
emitChange('rename', oldPath.toString()); | ||
} | ||
@@ -383,3 +399,3 @@ catch (e) { | ||
const { fs, path: resolved } = resolveMount(await realpath(path)); | ||
return await fs.exists(resolved, cred); | ||
return await fs.exists(resolved); | ||
} | ||
@@ -397,3 +413,6 @@ catch (e) { | ||
try { | ||
const stats = await fs.stat(resolved, cred); | ||
const stats = await fs.stat(resolved); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
} | ||
return options?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -410,3 +429,3 @@ } | ||
try { | ||
const stats = await fs.stat(resolved, cred); | ||
const stats = await fs.stat(resolved); | ||
return options?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -450,3 +469,7 @@ } | ||
try { | ||
await fs.unlink(resolved, cred); | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
} | ||
await fs.unlink(resolved); | ||
emitChange('rename', path.toString()); | ||
} | ||
@@ -467,3 +490,4 @@ catch (e) { | ||
const { fs, path: resolved } = resolveMount(path); | ||
if (!(await fs.exists(resolved, cred))) { | ||
const stats = await fs.stat(resolved).catch(() => null); | ||
if (!stats) { | ||
if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') { | ||
@@ -473,14 +497,18 @@ throw ErrnoError.With('ENOENT', path, '_open'); | ||
// Create the file | ||
const parentStats = await fs.stat(dirname(resolved), cred); | ||
if (parentStats && !parentStats.isDirectory()) { | ||
const parentStats = await fs.stat(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
} | ||
if (!parentStats.isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', dirname(path), '_open'); | ||
} | ||
return new FileHandle(await fs.createFile(resolved, flag, mode, cred)); | ||
return new FileHandle(await fs.createFile(resolved, flag, mode)); | ||
} | ||
if (!stats.hasAccess(flagToMode(flag), credentials)) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
} | ||
if (isExclusive(flag)) { | ||
throw ErrnoError.With('EEXIST', path, '_open'); | ||
} | ||
if (!isTruncating(flag)) { | ||
return new FileHandle(await fs.openFile(resolved, flag, cred)); | ||
} | ||
const handle = new FileHandle(await fs.openFile(resolved, flag)); | ||
/* | ||
@@ -492,6 +520,7 @@ In a previous implementation, we deleted the file and | ||
*/ | ||
const file = await fs.openFile(resolved, flag, cred); | ||
await file.truncate(0); | ||
await file.sync(); | ||
return new FileHandle(file); | ||
if (isTruncating(flag)) { | ||
await handle.truncate(0); | ||
await handle.sync(); | ||
} | ||
return handle; | ||
} | ||
@@ -605,3 +634,7 @@ /** | ||
try { | ||
await fs.rmdir(resolved, cred); | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', resolved, 'rmdir'); | ||
} | ||
await fs.rmdir(resolved); | ||
emitChange('rename', path.toString()); | ||
} | ||
@@ -622,6 +655,11 @@ catch (e) { | ||
if (!options?.recursive) { | ||
await fs.mkdir(resolved, mode, cred); | ||
if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
} | ||
await fs.mkdir(resolved, mode); | ||
emitChange('rename', path.toString()); | ||
return; | ||
} | ||
const dirs = []; | ||
for (let dir = resolved, origDir = path; !(await fs.exists(dir, cred)); dir = dirname(dir), origDir = dirname(origDir)) { | ||
for (let dir = resolved, origDir = path; !(await fs.exists(dir)); dir = dirname(dir), origDir = dirname(origDir)) { | ||
dirs.unshift(dir); | ||
@@ -631,3 +669,7 @@ errorPaths[dir] = origDir; | ||
for (const dir of dirs) { | ||
await fs.mkdir(dir, mode, cred); | ||
if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
} | ||
await fs.mkdir(dir, mode); | ||
emitChange('rename', dir); | ||
} | ||
@@ -643,2 +685,5 @@ return dirs[0]; | ||
path = normalizePath(path); | ||
if (!(await stat(path)).hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
} | ||
path = (await exists(path)) ? await realpath(path) : path; | ||
@@ -648,3 +693,3 @@ const { fs, path: resolved } = resolveMount(path); | ||
try { | ||
entries = await fs.readdir(resolved, cred); | ||
entries = await fs.readdir(resolved); | ||
} | ||
@@ -674,14 +719,27 @@ catch (e) { | ||
* `link`. | ||
* @param existing | ||
* @param newpath | ||
* @param targetPath | ||
* @param linkPath | ||
*/ | ||
export async function link(existing, newpath) { | ||
existing = normalizePath(existing); | ||
newpath = normalizePath(newpath); | ||
const { fs, path: resolved } = resolveMount(newpath); | ||
export async function link(targetPath, linkPath) { | ||
targetPath = normalizePath(targetPath); | ||
if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
} | ||
const { fs, path } = resolveMount(targetPath); | ||
const link = resolveMount(linkPath); | ||
if (fs != link.fs) { | ||
throw ErrnoError.With('EXDEV', linkPath, 'link'); | ||
} | ||
try { | ||
return await fs.link(existing, newpath, cred); | ||
if (!(await fs.stat(path)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
} | ||
return await fs.link(path, link.path); | ||
} | ||
catch (e) { | ||
throw fixError(e, { [resolved]: newpath }); | ||
throw fixError(e, { [link.path]: linkPath, [path]: targetPath }); | ||
} | ||
@@ -870,3 +928,3 @@ } | ||
try { | ||
const stats = await fs.stat(resolvedPath, cred); | ||
const stats = await fs.stat(resolvedPath); | ||
if (!stats.isSymbolicLink()) { | ||
@@ -885,3 +943,3 @@ return lpath; | ||
[Symbol.asyncIterator]() { | ||
const watcher = new FSWatcher(typeof options != 'string' ? options : { encoding: options }); | ||
const watcher = new FSWatcher(filename.toString(), typeof options != 'string' ? options : { encoding: options }); | ||
function withDone(done) { | ||
@@ -912,3 +970,3 @@ return function () { | ||
const stats = await stat(path); | ||
if (!stats.hasAccess(mode, cred)) { | ||
if (!stats.hasAccess(mode, credentials)) { | ||
throw new ErrnoError(Errno.EACCES); | ||
@@ -969,2 +1027,3 @@ } | ||
await writeFile(dest, await readFile(src)); | ||
emitChange('rename', dest.toString()); | ||
} | ||
@@ -981,5 +1040,3 @@ copyFile; | ||
path = normalizePath(path); | ||
const dir = new Dir(path); | ||
await dir._loadEntries(); | ||
return dir; | ||
return new Dir(path); | ||
} | ||
@@ -986,0 +1043,0 @@ opendir; |
/// <reference types="node" resolution-mode="require"/> | ||
import type { BigIntStatsFs, StatsFs } from 'node:fs'; | ||
import type { Cred } from '../cred.js'; | ||
import type { File } from '../file.js'; | ||
import type { FileSystem } from '../filesystem.js'; | ||
import { type AbsolutePath } from './path.js'; | ||
export declare let cred: Cred; | ||
export declare function setCred(val: Cred): void; | ||
export declare const fdMap: Map<number, File>; | ||
@@ -10,0 +7,0 @@ export declare function file2fd(file: File): number; |
// Utilities and shared data | ||
import { InMemory } from '../backends/memory.js'; | ||
import { rootCred } from '../cred.js'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
@@ -8,7 +7,2 @@ import { size_max } from '../inode.js'; | ||
import { resolve } from './path.js'; | ||
// credentials | ||
export let cred = rootCred; | ||
export function setCred(val) { | ||
cred = val; | ||
} | ||
// descriptors | ||
@@ -15,0 +9,0 @@ export const fdMap = new Map(); |
@@ -200,3 +200,2 @@ /// <reference types="node" resolution-mode="require"/> | ||
* @param mode defaults to o777 | ||
* @todo Implement recursion | ||
*/ | ||
@@ -234,6 +233,6 @@ export declare function mkdirSync(path: fs.PathLike, options: fs.MakeDirectoryOptions & { | ||
* Synchronous `link`. | ||
* @param existing | ||
* @param newpath | ||
* @param targetPath | ||
* @param linkPath | ||
*/ | ||
export declare function linkSync(existing: fs.PathLike, newpath: fs.PathLike): void; | ||
export declare function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void; | ||
/** | ||
@@ -240,0 +239,0 @@ * Synchronous `symlink`. |
@@ -48,9 +48,11 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js'; | ||
import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js'; | ||
import { BigIntStats } from '../stats.js'; | ||
import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js'; | ||
import { COPYFILE_EXCL, F_OK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK } from './constants.js'; | ||
import * as constants from './constants.js'; | ||
import { Dir, Dirent } from './dir.js'; | ||
import { dirname, join, parse } from './path.js'; | ||
import { _statfs, cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { credentials } from '../credentials.js'; | ||
import { emitChange } from './watchers.js'; | ||
/** | ||
@@ -64,14 +66,19 @@ * Synchronous rename. | ||
newPath = normalizePath(newPath); | ||
const _old = resolveMount(oldPath); | ||
const _new = resolveMount(newPath); | ||
const paths = { [_old.path]: oldPath, [_new.path]: newPath }; | ||
const oldMount = resolveMount(oldPath); | ||
const newMount = resolveMount(newPath); | ||
if (!statSync(dirname(oldPath)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
} | ||
try { | ||
if (_old === _new) { | ||
return _old.fs.renameSync(_old.path, _new.path, cred); | ||
if (oldMount === newMount) { | ||
oldMount.fs.renameSync(oldMount.path, newMount.path); | ||
emitChange('rename', oldPath.toString()); | ||
return; | ||
} | ||
writeFileSync(newPath, readFileSync(oldPath)); | ||
unlinkSync(oldPath); | ||
emitChange('rename', oldPath.toString()); | ||
} | ||
catch (e) { | ||
throw fixError(e, paths); | ||
throw fixError(e, { [oldMount.path]: oldPath, [newMount.path]: newPath }); | ||
} | ||
@@ -88,3 +95,3 @@ } | ||
const { fs, path: resolvedPath } = resolveMount(realpathSync(path)); | ||
return fs.existsSync(resolvedPath, cred); | ||
return fs.existsSync(resolvedPath); | ||
} | ||
@@ -103,3 +110,6 @@ catch (e) { | ||
try { | ||
const stats = fs.statSync(resolved, cred); | ||
const stats = fs.statSync(resolved); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
} | ||
return options?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -116,3 +126,3 @@ } | ||
try { | ||
const stats = fs.statSync(resolved, cred); | ||
const stats = fs.statSync(resolved); | ||
return options?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -157,3 +167,7 @@ } | ||
try { | ||
return fs.unlinkSync(resolved, cred); | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
} | ||
fs.unlinkSync(resolved); | ||
emitChange('rename', path.toString()); | ||
} | ||
@@ -170,3 +184,3 @@ catch (e) { | ||
const { fs, path: resolved } = resolveMount(path); | ||
if (!fs.existsSync(resolved, cred)) { | ||
if (!fs.existsSync(resolved)) { | ||
if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') { | ||
@@ -176,10 +190,13 @@ throw ErrnoError.With('ENOENT', path, '_open'); | ||
// Create the file | ||
const parentStats = fs.statSync(dirname(resolved), cred); | ||
const parentStats = fs.statSync(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
} | ||
if (!parentStats.isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', dirname(path), '_open'); | ||
} | ||
return fs.createFileSync(resolved, flag, mode, cred); | ||
return fs.createFileSync(resolved, flag, mode); | ||
} | ||
const stats = fs.statSync(resolved, cred); | ||
if (!stats.hasAccess(mode, cred)) { | ||
const stats = fs.statSync(resolved); | ||
if (!stats.hasAccess(mode, credentials) || !stats.hasAccess(flagToMode(flag), credentials)) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
@@ -190,14 +207,8 @@ } | ||
} | ||
if (!isTruncating(flag)) { | ||
return fs.openFileSync(resolved, flag, cred); | ||
const file = fs.openFileSync(resolved, flag); | ||
if (isTruncating(flag)) { | ||
file.truncateSync(0); | ||
file.syncSync(); | ||
} | ||
// Delete file. | ||
fs.unlinkSync(resolved, cred); | ||
/* | ||
Create file. Use the same mode as the old file. | ||
Node itself modifies the ctime when this occurs, so this action | ||
will preserve that behavior if the underlying file system | ||
supports those properties. | ||
*/ | ||
return fs.createFileSync(resolved, flag, stats.mode, cred); | ||
return file; | ||
} | ||
@@ -212,3 +223,3 @@ /** | ||
*/ | ||
export function openSync(path, flag, mode = F_OK) { | ||
export function openSync(path, flag, mode = constants.F_OK) { | ||
return file2fd(_openSync(path, flag, mode, true)); | ||
@@ -273,2 +284,3 @@ } | ||
file.writeSync(encodedData, 0, encodedData.byteLength, 0); | ||
emitChange('change', path.toString()); | ||
} | ||
@@ -381,3 +393,5 @@ catch (e_3) { | ||
position ?? (position = file.position); | ||
return file.writeSync(buffer, offset, length, position); | ||
const bytesWritten = file.writeSync(buffer, offset, length, position); | ||
emitChange('change', file.path); | ||
return bytesWritten; | ||
} | ||
@@ -441,3 +455,7 @@ writeSync; | ||
try { | ||
fs.rmdirSync(resolved, cred); | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', resolved, 'rmdir'); | ||
} | ||
fs.rmdirSync(resolved); | ||
emitChange('rename', path.toString()); | ||
} | ||
@@ -458,6 +476,9 @@ catch (e) { | ||
if (!options?.recursive) { | ||
return fs.mkdirSync(resolved, mode, cred); | ||
if (!fs.statSync(dirname(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
} | ||
return fs.mkdirSync(resolved, mode); | ||
} | ||
const dirs = []; | ||
for (let dir = resolved, original = path; !fs.existsSync(dir, cred); dir = dirname(dir), original = dirname(original)) { | ||
for (let dir = resolved, original = path; !fs.existsSync(dir); dir = dirname(dir), original = dirname(original)) { | ||
dirs.unshift(dir); | ||
@@ -467,3 +488,7 @@ errorPaths[dir] = original; | ||
for (const dir of dirs) { | ||
fs.mkdirSync(dir, mode, cred); | ||
if (!fs.statSync(dirname(dir)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
} | ||
fs.mkdirSync(dir, mode); | ||
emitChange('rename', dir); | ||
} | ||
@@ -481,4 +506,7 @@ return dirs[0]; | ||
let entries; | ||
if (!statSync(path).hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
} | ||
try { | ||
entries = fs.readdirSync(resolved, cred); | ||
entries = fs.readdirSync(resolved); | ||
} | ||
@@ -513,14 +541,27 @@ catch (e) { | ||
* Synchronous `link`. | ||
* @param existing | ||
* @param newpath | ||
* @param targetPath | ||
* @param linkPath | ||
*/ | ||
export function linkSync(existing, newpath) { | ||
existing = normalizePath(existing); | ||
newpath = normalizePath(newpath); | ||
const { fs, path: resolved } = resolveMount(existing); | ||
export function linkSync(targetPath, linkPath) { | ||
targetPath = normalizePath(targetPath); | ||
if (!statSync(dirname(targetPath)).hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!statSync(dirname(linkPath)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
} | ||
const { fs, path } = resolveMount(targetPath); | ||
const link = resolveMount(linkPath); | ||
if (fs != link.fs) { | ||
throw ErrnoError.With('EXDEV', linkPath, 'link'); | ||
} | ||
try { | ||
return fs.linkSync(resolved, newpath, cred); | ||
if (!fs.statSync(path).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
} | ||
return fs.linkSync(path, linkPath); | ||
} | ||
catch (e) { | ||
throw fixError(e, { [resolved]: existing }); | ||
throw fixError(e, { [path]: targetPath, [link.path]: linkPath }); | ||
} | ||
@@ -544,3 +585,3 @@ } | ||
const file = _openSync(path, 'r+', 0o644, false); | ||
file._setTypeSync(S_IFLNK); | ||
file._setTypeSync(constants.S_IFLNK); | ||
} | ||
@@ -634,3 +675,3 @@ symlinkSync; | ||
try { | ||
const stats = fs.statSync(resolvedPath, cred); | ||
const stats = fs.statSync(resolvedPath); | ||
if (!stats.isSymbolicLink()) { | ||
@@ -653,3 +694,3 @@ return lpath; | ||
const stats = statSync(path); | ||
if (!stats.hasAccess(mode, cred)) { | ||
if (!stats.hasAccess(mode, credentials)) { | ||
throw new ErrnoError(Errno.EACCES); | ||
@@ -666,4 +707,4 @@ } | ||
const stats = statSync(path); | ||
switch (stats.mode & S_IFMT) { | ||
case S_IFDIR: | ||
switch (stats.mode & constants.S_IFMT) { | ||
case constants.S_IFDIR: | ||
if (options?.recursive) { | ||
@@ -676,10 +717,10 @@ for (const entry of readdirSync(path)) { | ||
return; | ||
case S_IFREG: | ||
case S_IFLNK: | ||
case constants.S_IFREG: | ||
case constants.S_IFLNK: | ||
unlinkSync(path); | ||
return; | ||
case S_IFBLK: | ||
case S_IFCHR: | ||
case S_IFIFO: | ||
case S_IFSOCK: | ||
case constants.S_IFBLK: | ||
case constants.S_IFCHR: | ||
case constants.S_IFIFO: | ||
case constants.S_IFSOCK: | ||
default: | ||
@@ -708,6 +749,7 @@ throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm'); | ||
dest = normalizePath(dest); | ||
if (flags && flags & COPYFILE_EXCL && existsSync(dest)) { | ||
if (flags && flags & constants.COPYFILE_EXCL && existsSync(dest)) { | ||
throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', dest, 'copyFile'); | ||
} | ||
writeFileSync(dest, readFileSync(src)); | ||
emitChange('rename', dest.toString()); | ||
} | ||
@@ -755,3 +797,3 @@ copyFileSync; | ||
path = normalizePath(path); | ||
return new Dir(path); // Re-use existing `Dir` class | ||
return new Dir(path); | ||
} | ||
@@ -778,4 +820,4 @@ opendirSync; | ||
} | ||
switch (srcStats.mode & S_IFMT) { | ||
case S_IFDIR: | ||
switch (srcStats.mode & constants.S_IFMT) { | ||
case constants.S_IFDIR: | ||
if (!opts?.recursive) { | ||
@@ -792,10 +834,10 @@ throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp'); | ||
break; | ||
case S_IFREG: | ||
case S_IFLNK: | ||
case constants.S_IFREG: | ||
case constants.S_IFLNK: | ||
copyFileSync(source, destination); | ||
break; | ||
case S_IFBLK: | ||
case S_IFCHR: | ||
case S_IFIFO: | ||
case S_IFSOCK: | ||
case constants.S_IFBLK: | ||
case constants.S_IFCHR: | ||
case constants.S_IFIFO: | ||
case constants.S_IFSOCK: | ||
default: | ||
@@ -802,0 +844,0 @@ throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm'); |
@@ -7,5 +7,14 @@ /// <reference types="node" resolution-mode="require"/> | ||
import type * as fs from 'node:fs'; | ||
import { type Stats } from '../stats.js'; | ||
/** | ||
* Base class for file system watchers. | ||
* Provides event handling capabilities for watching file system changes. | ||
* | ||
* @template TEvents The type of events emitted by the watcher. | ||
*/ | ||
declare class Watcher<TEvents extends Record<string, unknown[]> = Record<string, unknown[]>> extends EventEmitter<TEvents> implements NodeEventEmitter { | ||
readonly path: string; | ||
off<T extends EventEmitter.EventNames<TEvents>>(event: T, fn?: (...args: any[]) => void, context?: any, once?: boolean): this; | ||
removeListener<T extends EventEmitter.EventNames<TEvents>>(event: T, fn?: (...args: any[]) => void, context?: any, once?: boolean): this; | ||
constructor(path: string); | ||
setMaxListeners(): never; | ||
@@ -20,3 +29,5 @@ getMaxListeners(): never; | ||
/** | ||
* @todo Actually emit events | ||
* Watches for changes on the file system. | ||
* | ||
* @template T The type of the filename, either `string` or `Buffer`. | ||
*/ | ||
@@ -29,7 +40,33 @@ export declare class FSWatcher<T extends string | Buffer = string | Buffer> extends Watcher<{ | ||
readonly options: fs.WatchOptions; | ||
constructor(options: fs.WatchOptions); | ||
constructor(path: string, options: fs.WatchOptions); | ||
close(): void; | ||
[Symbol.dispose](): void; | ||
} | ||
export declare class StatWatcher extends Watcher implements fs.StatWatcher { | ||
/** | ||
* Watches for changes to a file's stats. | ||
* | ||
* Instances of `StatWatcher` are used by `fs.watchFile()` to monitor changes to a file's statistics. | ||
*/ | ||
export declare class StatWatcher extends Watcher<{ | ||
change: [current: Stats, previous: Stats]; | ||
close: []; | ||
error: [error: Error]; | ||
}> implements fs.StatWatcher { | ||
private options; | ||
private intervalId?; | ||
private previous?; | ||
constructor(path: string, options: { | ||
persistent?: boolean; | ||
interval?: number; | ||
}); | ||
protected onInterval(): void; | ||
protected start(): void; | ||
/** | ||
* @internal | ||
*/ | ||
stop(): void; | ||
} | ||
export declare function addWatcher(path: string, watcher: FSWatcher): void; | ||
export declare function removeWatcher(path: string, watcher: FSWatcher): void; | ||
export declare function emitChange(eventType: fs.WatchEventType, filename: string): void; | ||
export {}; |
import { EventEmitter } from 'eventemitter3'; | ||
import { ErrnoError } from '../error.js'; | ||
import { isStatsEqual } from '../stats.js'; | ||
import { normalizePath } from '../utils.js'; | ||
import { dirname, basename } from './path.js'; | ||
import { statSync } from './sync.js'; | ||
/** | ||
* Base class for file system watchers. | ||
* Provides event handling capabilities for watching file system changes. | ||
* | ||
* @template TEvents The type of events emitted by the watcher. | ||
*/ | ||
class Watcher extends EventEmitter { | ||
@@ -12,16 +22,20 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
/* eslint-enable @typescript-eslint/no-explicit-any */ | ||
constructor(path) { | ||
super(); | ||
this.path = path; | ||
} | ||
setMaxListeners() { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.setMaxListeners'); | ||
} | ||
getMaxListeners() { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.getMaxListeners'); | ||
} | ||
prependListener() { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.prependListener'); | ||
} | ||
prependOnceListener() { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.prependOnceListener'); | ||
} | ||
rawListeners() { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.rawListeners'); | ||
} | ||
@@ -36,12 +50,104 @@ ref() { | ||
/** | ||
* @todo Actually emit events | ||
* Watches for changes on the file system. | ||
* | ||
* @template T The type of the filename, either `string` or `Buffer`. | ||
*/ | ||
export class FSWatcher extends Watcher { | ||
constructor(options) { | ||
super(); | ||
constructor(path, options) { | ||
super(path); | ||
this.options = options; | ||
addWatcher(path.toString(), this); | ||
} | ||
close() { } | ||
close() { | ||
super.emit('close'); | ||
removeWatcher(this.path.toString(), this); | ||
} | ||
[Symbol.dispose]() { | ||
this.close(); | ||
} | ||
} | ||
/** | ||
* Watches for changes to a file's stats. | ||
* | ||
* Instances of `StatWatcher` are used by `fs.watchFile()` to monitor changes to a file's statistics. | ||
*/ | ||
export class StatWatcher extends Watcher { | ||
constructor(path, options) { | ||
super(path); | ||
this.options = options; | ||
this.start(); | ||
} | ||
onInterval() { | ||
try { | ||
const current = statSync(this.path); | ||
if (!isStatsEqual(this.previous, current)) { | ||
this.emit('change', current, this.previous); | ||
this.previous = current; | ||
} | ||
} | ||
catch (e) { | ||
this.emit('error', e); | ||
} | ||
} | ||
start() { | ||
const interval = this.options.interval || 5000; | ||
try { | ||
this.previous = statSync(this.path); | ||
} | ||
catch (e) { | ||
this.emit('error', e); | ||
return; | ||
} | ||
this.intervalId = setInterval(this.onInterval.bind(this), interval); | ||
if (!this.options.persistent && typeof this.intervalId == 'object') { | ||
this.intervalId.unref(); | ||
} | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
stop() { | ||
if (this.intervalId) { | ||
clearInterval(this.intervalId); | ||
this.intervalId = undefined; | ||
} | ||
this.removeAllListeners(); | ||
} | ||
} | ||
const watchers = new Map(); | ||
export function addWatcher(path, watcher) { | ||
const normalizedPath = normalizePath(path); | ||
if (!watchers.has(normalizedPath)) { | ||
watchers.set(normalizedPath, new Set()); | ||
} | ||
watchers.get(normalizedPath).add(watcher); | ||
} | ||
export function removeWatcher(path, watcher) { | ||
const normalizedPath = normalizePath(path); | ||
if (watchers.has(normalizedPath)) { | ||
watchers.get(normalizedPath).delete(watcher); | ||
if (watchers.get(normalizedPath).size === 0) { | ||
watchers.delete(normalizedPath); | ||
} | ||
} | ||
} | ||
export function emitChange(eventType, filename) { | ||
let normalizedFilename = normalizePath(filename); | ||
// Notify watchers on the specific file | ||
if (watchers.has(normalizedFilename)) { | ||
for (const watcher of watchers.get(normalizedFilename)) { | ||
watcher.emit('change', eventType, basename(filename)); | ||
} | ||
} | ||
// Notify watchers on parent directories if they are watching recursively | ||
let parent = dirname(normalizedFilename); | ||
while (parent !== normalizedFilename && parent !== '/') { | ||
if (watchers.has(parent)) { | ||
for (const watcher of watchers.get(parent)) { | ||
watcher.emit('change', eventType, basename(filename)); | ||
} | ||
} | ||
normalizedFilename = parent; | ||
parent = dirname(parent); | ||
} | ||
} |
@@ -195,3 +195,3 @@ /// <reference types="node" resolution-mode="require"/> | ||
/** | ||
* @return A friendly error message. | ||
* @returns A friendly error message. | ||
*/ | ||
@@ -198,0 +198,0 @@ toString(): string; |
@@ -269,3 +269,3 @@ /** | ||
/** | ||
* @return A friendly error message. | ||
* @returns A friendly error message. | ||
*/ | ||
@@ -272,0 +272,0 @@ toString() { |
@@ -1,2 +0,1 @@ | ||
import type { Cred } from './cred.js'; | ||
import type { File } from './file.js'; | ||
@@ -78,15 +77,15 @@ import { type Stats } from './stats.js'; | ||
*/ | ||
abstract rename(oldPath: string, newPath: string, cred: Cred): Promise<void>; | ||
abstract rename(oldPath: string, newPath: string): Promise<void>; | ||
/** | ||
* Synchronous rename. | ||
*/ | ||
abstract renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
abstract renameSync(oldPath: string, newPath: string): void; | ||
/** | ||
* Asynchronous `stat`. | ||
*/ | ||
abstract stat(path: string, cred: Cred): Promise<Stats>; | ||
abstract stat(path: string): Promise<Stats>; | ||
/** | ||
* Synchronous `stat`. | ||
*/ | ||
abstract statSync(path: string, cred: Cred): Stats; | ||
abstract statSync(path: string): Stats; | ||
/** | ||
@@ -97,3 +96,3 @@ * Opens the file at `path` with the given flag. The file must exist. | ||
*/ | ||
abstract openFile(path: string, flag: string, cred: Cred): Promise<File>; | ||
abstract openFile(path: string, flag: string): Promise<File>; | ||
/** | ||
@@ -105,27 +104,27 @@ * Opens the file at `path` with the given flag. The file must exist. | ||
*/ | ||
abstract openFileSync(path: string, flag: string, cred: Cred): File; | ||
abstract openFileSync(path: string, flag: string): File; | ||
/** | ||
* Create the file at `path` with the given mode. Then, open it with the given flag. | ||
*/ | ||
abstract createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>; | ||
abstract createFile(path: string, flag: string, mode: number): Promise<File>; | ||
/** | ||
* Create the file at `path` with the given mode. Then, open it with the given flag. | ||
*/ | ||
abstract createFileSync(path: string, flag: string, mode: number, cred: Cred): File; | ||
abstract createFileSync(path: string, flag: string, mode: number): File; | ||
/** | ||
* Asynchronous `unlink`. | ||
*/ | ||
abstract unlink(path: string, cred: Cred): Promise<void>; | ||
abstract unlink(path: string): Promise<void>; | ||
/** | ||
* Synchronous `unlink`. | ||
*/ | ||
abstract unlinkSync(path: string, cred: Cred): void; | ||
abstract unlinkSync(path: string): void; | ||
/** | ||
* Asynchronous `rmdir`. | ||
*/ | ||
abstract rmdir(path: string, cred: Cred): Promise<void>; | ||
abstract rmdir(path: string): Promise<void>; | ||
/** | ||
* Synchronous `rmdir`. | ||
*/ | ||
abstract rmdirSync(path: string, cred: Cred): void; | ||
abstract rmdirSync(path: string): void; | ||
/** | ||
@@ -135,3 +134,3 @@ * Asynchronous `mkdir`. | ||
*/ | ||
abstract mkdir(path: string, mode: number, cred: Cred): Promise<void>; | ||
abstract mkdir(path: string, mode: number): Promise<void>; | ||
/** | ||
@@ -141,27 +140,27 @@ * Synchronous `mkdir`. | ||
*/ | ||
abstract mkdirSync(path: string, mode: number, cred: Cred): void; | ||
abstract mkdirSync(path: string, mode: number): void; | ||
/** | ||
* Asynchronous `readdir`. Reads the contents of a directory. | ||
*/ | ||
abstract readdir(path: string, cred: Cred): Promise<string[]>; | ||
abstract readdir(path: string): Promise<string[]>; | ||
/** | ||
* Synchronous `readdir`. Reads the contents of a directory. | ||
*/ | ||
abstract readdirSync(path: string, cred: Cred): string[]; | ||
abstract readdirSync(path: string): string[]; | ||
/** | ||
* Test whether or not the given path exists. | ||
*/ | ||
exists(path: string, cred: Cred): Promise<boolean>; | ||
exists(path: string): Promise<boolean>; | ||
/** | ||
* Test whether or not the given path exists. | ||
*/ | ||
existsSync(path: string, cred: Cred): boolean; | ||
existsSync(path: string): boolean; | ||
/** | ||
* Asynchronous `link`. | ||
*/ | ||
abstract link(srcpath: string, dstpath: string, cred: Cred): Promise<void>; | ||
abstract link(target: string, link: string): Promise<void>; | ||
/** | ||
* Synchronous `link`. | ||
*/ | ||
abstract linkSync(srcpath: string, dstpath: string, cred: Cred): void; | ||
abstract linkSync(target: string, link: string): void; | ||
/** | ||
@@ -168,0 +167,0 @@ * Synchronize the data and stats for path asynchronously |
@@ -30,5 +30,5 @@ import { ZenFsType } from './stats.js'; | ||
*/ | ||
async exists(path, cred) { | ||
async exists(path) { | ||
try { | ||
await this.stat(path, cred); | ||
await this.stat(path); | ||
return true; | ||
@@ -43,5 +43,5 @@ } | ||
*/ | ||
existsSync(path, cred) { | ||
existsSync(path) { | ||
try { | ||
this.statSync(path, cred); | ||
this.statSync(path); | ||
return true; | ||
@@ -48,0 +48,0 @@ } |
@@ -12,3 +12,3 @@ export * from './error.js'; | ||
export * from './config.js'; | ||
export * from './cred.js'; | ||
export * from './credentials.js'; | ||
export * from './file.js'; | ||
@@ -15,0 +15,0 @@ export * from './filesystem.js'; |
@@ -12,3 +12,3 @@ export * from './error.js'; | ||
export * from './config.js'; | ||
export * from './cred.js'; | ||
export * from './credentials.js'; | ||
export * from './file.js'; | ||
@@ -15,0 +15,0 @@ export * from './filesystem.js'; |
@@ -1,2 +0,1 @@ | ||
import { type Cred } from '../cred.js'; | ||
import { type File } from '../file.js'; | ||
@@ -16,7 +15,7 @@ import type { FileSystem } from '../filesystem.js'; | ||
* Implementing classes must define `_sync` for the synchronous file system used as a cache. | ||
* Synchronous methods on an asynchronous FS are implemented by: | ||
* - Performing operations over the in-memory copy, | ||
* while asynchronously pipelining them to the backing store. | ||
* - During loading, the contents of the async file system are preloaded into the synchronous store. | ||
* | ||
* Synchronous methods on an asynchronous FS are implemented by performing operations over the in-memory copy, | ||
* while asynchronously pipelining them to the backing store. | ||
* During loading, the contents of the async file system are preloaded into the synchronous store. | ||
* | ||
*/ | ||
@@ -30,12 +29,12 @@ export declare function Async<T extends typeof FileSystem>(FS: T): Mixin<T, { | ||
ready(): Promise<void>; | ||
renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
statSync(path: string, cred: Cred): Stats; | ||
createFileSync(path: string, flag: string, mode: number, cred: Cred): File; | ||
openFileSync(path: string, flag: string, cred: Cred): File; | ||
unlinkSync(path: string, cred: Cred): void; | ||
rmdirSync(path: string, cred: Cred): void; | ||
mkdirSync(path: string, mode: number, cred: Cred): void; | ||
readdirSync(path: string, cred: Cred): string[]; | ||
linkSync(srcpath: string, dstpath: string, cred: Cred): void; | ||
renameSync(oldPath: string, newPath: string): void; | ||
statSync(path: string): Stats; | ||
createFileSync(path: string, flag: string, mode: number): File; | ||
openFileSync(path: string, flag: string): File; | ||
unlinkSync(path: string): void; | ||
rmdirSync(path: string): void; | ||
mkdirSync(path: string, mode: number): void; | ||
readdirSync(path: string): string[]; | ||
linkSync(srcpath: string, dstpath: string): void; | ||
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void; | ||
}>; |
@@ -46,6 +46,5 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
}); | ||
import { rootCred } from '../cred.js'; | ||
import { join } from '../emulation/path.js'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { PreloadFile, parseFlag } from '../file.js'; | ||
import { parseFlag, PreloadFile } from '../file.js'; | ||
/** | ||
@@ -55,7 +54,7 @@ * Async() implements synchronous methods on an asynchronous file system | ||
* Implementing classes must define `_sync` for the synchronous file system used as a cache. | ||
* Synchronous methods on an asynchronous FS are implemented by: | ||
* - Performing operations over the in-memory copy, | ||
* while asynchronously pipelining them to the backing store. | ||
* - During loading, the contents of the async file system are preloaded into the synchronous store. | ||
* | ||
* Synchronous methods on an asynchronous FS are implemented by performing operations over the in-memory copy, | ||
* while asynchronously pipelining them to the backing store. | ||
* During loading, the contents of the async file system are preloaded into the synchronous store. | ||
* | ||
*/ | ||
@@ -105,20 +104,20 @@ export function Async(FS) { | ||
} | ||
renameSync(oldPath, newPath, cred) { | ||
renameSync(oldPath, newPath) { | ||
this.checkSync(oldPath, 'rename'); | ||
this._sync.renameSync(oldPath, newPath, cred); | ||
this.queue('rename', oldPath, newPath, cred); | ||
this._sync.renameSync(oldPath, newPath); | ||
this.queue('rename', oldPath, newPath); | ||
} | ||
statSync(path, cred) { | ||
statSync(path) { | ||
this.checkSync(path, 'stat'); | ||
return this._sync.statSync(path, cred); | ||
return this._sync.statSync(path); | ||
} | ||
createFileSync(path, flag, mode, cred) { | ||
createFileSync(path, flag, mode) { | ||
this.checkSync(path, 'createFile'); | ||
this._sync.createFileSync(path, flag, mode, cred); | ||
this.queue('createFile', path, flag, mode, cred); | ||
return this.openFileSync(path, flag, cred); | ||
this._sync.createFileSync(path, flag, mode); | ||
this.queue('createFile', path, flag, mode); | ||
return this.openFileSync(path, flag); | ||
} | ||
openFileSync(path, flag, cred) { | ||
openFileSync(path, flag) { | ||
this.checkSync(path, 'openFile'); | ||
const file = this._sync.openFileSync(path, flag, cred); | ||
const file = this._sync.openFileSync(path, flag); | ||
const stats = file.statSync(); | ||
@@ -129,25 +128,25 @@ const buffer = new Uint8Array(stats.size); | ||
} | ||
unlinkSync(path, cred) { | ||
unlinkSync(path) { | ||
this.checkSync(path, 'unlinkSync'); | ||
this._sync.unlinkSync(path, cred); | ||
this.queue('unlink', path, cred); | ||
this._sync.unlinkSync(path); | ||
this.queue('unlink', path); | ||
} | ||
rmdirSync(path, cred) { | ||
rmdirSync(path) { | ||
this.checkSync(path, 'rmdir'); | ||
this._sync.rmdirSync(path, cred); | ||
this.queue('rmdir', path, cred); | ||
this._sync.rmdirSync(path); | ||
this.queue('rmdir', path); | ||
} | ||
mkdirSync(path, mode, cred) { | ||
mkdirSync(path, mode) { | ||
this.checkSync(path, 'mkdir'); | ||
this._sync.mkdirSync(path, mode, cred); | ||
this.queue('mkdir', path, mode, cred); | ||
this._sync.mkdirSync(path, mode); | ||
this.queue('mkdir', path, mode); | ||
} | ||
readdirSync(path, cred) { | ||
readdirSync(path) { | ||
this.checkSync(path, 'readdir'); | ||
return this._sync.readdirSync(path, cred); | ||
return this._sync.readdirSync(path); | ||
} | ||
linkSync(srcpath, dstpath, cred) { | ||
linkSync(srcpath, dstpath) { | ||
this.checkSync(srcpath, 'link'); | ||
this._sync.linkSync(srcpath, dstpath, cred); | ||
this.queue('link', srcpath, dstpath, cred); | ||
this._sync.linkSync(srcpath, dstpath); | ||
this.queue('link', srcpath, dstpath); | ||
} | ||
@@ -159,5 +158,5 @@ syncSync(path, data, stats) { | ||
} | ||
existsSync(path, cred) { | ||
existsSync(path) { | ||
this.checkSync(path, 'exists'); | ||
return this._sync.existsSync(path, cred); | ||
return this._sync.existsSync(path); | ||
} | ||
@@ -169,21 +168,12 @@ /** | ||
this.checkSync(path, 'crossCopy'); | ||
const stats = await this.stat(path, rootCred); | ||
if (stats.isDirectory()) { | ||
if (path !== '/') { | ||
const stats = await this.stat(path, rootCred); | ||
this._sync.mkdirSync(path, stats.mode, stats.cred()); | ||
} | ||
const files = await this.readdir(path, rootCred); | ||
for (const file of files) { | ||
await this.crossCopy(join(path, file)); | ||
} | ||
} | ||
else { | ||
const stats = await this.stat(path); | ||
if (!stats.isDirectory()) { | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const asyncFile = __addDisposableResource(env_1, await this.openFile(path, parseFlag('r'), rootCred), true); | ||
const syncFile = __addDisposableResource(env_1, this._sync.createFileSync(path, parseFlag('w'), stats.mode, stats.cred()), false); | ||
const asyncFile = __addDisposableResource(env_1, await this.openFile(path, parseFlag('r')), true); | ||
const syncFile = __addDisposableResource(env_1, this._sync.createFileSync(path, parseFlag('w'), stats.mode), false); | ||
const buffer = new Uint8Array(stats.size); | ||
await asyncFile.read(buffer); | ||
syncFile.writeSync(buffer, 0, stats.size); | ||
return; | ||
} | ||
@@ -200,2 +190,10 @@ catch (e_1) { | ||
} | ||
if (path !== '/') { | ||
const stats = await this.stat(path); | ||
this._sync.mkdirSync(path, stats.mode); | ||
} | ||
const files = await this.readdir(path); | ||
for (const file of files) { | ||
await this.crossCopy(join(path, file)); | ||
} | ||
} | ||
@@ -202,0 +200,0 @@ /** |
@@ -30,5 +30,5 @@ import type { FileSystem } from '../filesystem.js'; | ||
export declare function Mutexed<T extends new (...args: any[]) => FileSystem>(FS: T): Mixin<T, { | ||
lock(path: string): Promise<MutexLock>; | ||
lock(path: string, syscall: string): Promise<MutexLock>; | ||
lockSync(path: string): MutexLock; | ||
isLocked(path: string): boolean; | ||
}>; |
@@ -109,5 +109,13 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
*/ | ||
async lock(path) { | ||
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(); | ||
@@ -124,3 +132,3 @@ return lock; | ||
// Non-null assertion: we already checked locks has path | ||
throw ErrnoError.With('EBUSY', path, 'lockSync'); | ||
throw ErrnoError.With('EBUSY', path, 'lock'); | ||
} | ||
@@ -137,8 +145,8 @@ return this.addLock(path); | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
async rename(oldPath, newPath, cred) { | ||
async rename(oldPath, newPath) { | ||
const env_1 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_1, await this.lock(oldPath), false); | ||
const _ = __addDisposableResource(env_1, await this.lock(oldPath, 'rename'), false); | ||
// @ts-expect-error 2513 | ||
await super.rename(oldPath, newPath, cred); | ||
await super.rename(oldPath, newPath); | ||
} | ||
@@ -153,3 +161,3 @@ catch (e_1) { | ||
} | ||
renameSync(oldPath, newPath, cred) { | ||
renameSync(oldPath, newPath) { | ||
const env_2 = { stack: [], error: void 0, hasError: false }; | ||
@@ -159,3 +167,3 @@ try { | ||
// @ts-expect-error 2513 | ||
return super.renameSync(oldPath, newPath, cred); | ||
return super.renameSync(oldPath, newPath); | ||
} | ||
@@ -170,8 +178,8 @@ catch (e_2) { | ||
} | ||
async stat(path, cred) { | ||
async stat(path) { | ||
const env_3 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_3, await this.lock(path), false); | ||
const _ = __addDisposableResource(env_3, await this.lock(path, 'stat'), false); | ||
// @ts-expect-error 2513 | ||
return await super.stat(path, cred); | ||
return await super.stat(path); | ||
} | ||
@@ -186,3 +194,3 @@ catch (e_3) { | ||
} | ||
statSync(path, cred) { | ||
statSync(path) { | ||
const env_4 = { stack: [], error: void 0, hasError: false }; | ||
@@ -192,3 +200,3 @@ try { | ||
// @ts-expect-error 2513 | ||
return super.statSync(path, cred); | ||
return super.statSync(path); | ||
} | ||
@@ -203,8 +211,8 @@ catch (e_4) { | ||
} | ||
async openFile(path, flag, cred) { | ||
async openFile(path, flag) { | ||
const env_5 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_5, await this.lock(path), false); | ||
const _ = __addDisposableResource(env_5, await this.lock(path, 'openFile'), false); | ||
// @ts-expect-error 2513 | ||
return await super.openFile(path, flag, cred); | ||
return await super.openFile(path, flag); | ||
} | ||
@@ -219,3 +227,3 @@ catch (e_5) { | ||
} | ||
openFileSync(path, flag, cred) { | ||
openFileSync(path, flag) { | ||
const env_6 = { stack: [], error: void 0, hasError: false }; | ||
@@ -225,3 +233,3 @@ try { | ||
// @ts-expect-error 2513 | ||
return super.openFileSync(path, flag, cred); | ||
return super.openFileSync(path, flag); | ||
} | ||
@@ -236,8 +244,8 @@ catch (e_6) { | ||
} | ||
async createFile(path, flag, mode, cred) { | ||
async createFile(path, flag, mode) { | ||
const env_7 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_7, await this.lock(path), false); | ||
const _ = __addDisposableResource(env_7, await this.lock(path, 'createFile'), false); | ||
// @ts-expect-error 2513 | ||
return await super.createFile(path, flag, mode, cred); | ||
return await super.createFile(path, flag, mode); | ||
} | ||
@@ -252,3 +260,3 @@ catch (e_7) { | ||
} | ||
createFileSync(path, flag, mode, cred) { | ||
createFileSync(path, flag, mode) { | ||
const env_8 = { stack: [], error: void 0, hasError: false }; | ||
@@ -258,3 +266,3 @@ try { | ||
// @ts-expect-error 2513 | ||
return super.createFileSync(path, flag, mode, cred); | ||
return super.createFileSync(path, flag, mode); | ||
} | ||
@@ -269,8 +277,8 @@ catch (e_8) { | ||
} | ||
async unlink(path, cred) { | ||
async unlink(path) { | ||
const env_9 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_9, await this.lock(path), false); | ||
const _ = __addDisposableResource(env_9, await this.lock(path, 'unlink'), false); | ||
// @ts-expect-error 2513 | ||
await super.unlink(path, cred); | ||
await super.unlink(path); | ||
} | ||
@@ -285,3 +293,3 @@ catch (e_9) { | ||
} | ||
unlinkSync(path, cred) { | ||
unlinkSync(path) { | ||
const env_10 = { stack: [], error: void 0, hasError: false }; | ||
@@ -291,3 +299,3 @@ try { | ||
// @ts-expect-error 2513 | ||
return super.unlinkSync(path, cred); | ||
return super.unlinkSync(path); | ||
} | ||
@@ -302,8 +310,8 @@ catch (e_10) { | ||
} | ||
async rmdir(path, cred) { | ||
async rmdir(path) { | ||
const env_11 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_11, await this.lock(path), false); | ||
const _ = __addDisposableResource(env_11, await this.lock(path, 'rmdir'), false); | ||
// @ts-expect-error 2513 | ||
await super.rmdir(path, cred); | ||
await super.rmdir(path); | ||
} | ||
@@ -318,3 +326,3 @@ catch (e_11) { | ||
} | ||
rmdirSync(path, cred) { | ||
rmdirSync(path) { | ||
const env_12 = { stack: [], error: void 0, hasError: false }; | ||
@@ -324,3 +332,3 @@ try { | ||
// @ts-expect-error 2513 | ||
return super.rmdirSync(path, cred); | ||
return super.rmdirSync(path); | ||
} | ||
@@ -335,8 +343,8 @@ catch (e_12) { | ||
} | ||
async mkdir(path, mode, cred) { | ||
async mkdir(path, mode) { | ||
const env_13 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_13, await this.lock(path), false); | ||
const _ = __addDisposableResource(env_13, await this.lock(path, 'mkdir'), false); | ||
// @ts-expect-error 2513 | ||
await super.mkdir(path, mode, cred); | ||
await super.mkdir(path, mode); | ||
} | ||
@@ -351,3 +359,3 @@ catch (e_13) { | ||
} | ||
mkdirSync(path, mode, cred) { | ||
mkdirSync(path, mode) { | ||
const env_14 = { stack: [], error: void 0, hasError: false }; | ||
@@ -357,3 +365,3 @@ try { | ||
// @ts-expect-error 2513 | ||
return super.mkdirSync(path, mode, cred); | ||
return super.mkdirSync(path, mode); | ||
} | ||
@@ -368,8 +376,8 @@ catch (e_14) { | ||
} | ||
async readdir(path, cred) { | ||
async readdir(path) { | ||
const env_15 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_15, await this.lock(path), false); | ||
const _ = __addDisposableResource(env_15, await this.lock(path, 'readdir'), false); | ||
// @ts-expect-error 2513 | ||
return await super.readdir(path, cred); | ||
return await super.readdir(path); | ||
} | ||
@@ -384,3 +392,3 @@ catch (e_15) { | ||
} | ||
readdirSync(path, cred) { | ||
readdirSync(path) { | ||
const env_16 = { stack: [], error: void 0, hasError: false }; | ||
@@ -390,3 +398,3 @@ try { | ||
// @ts-expect-error 2513 | ||
return super.readdirSync(path, cred); | ||
return super.readdirSync(path); | ||
} | ||
@@ -401,7 +409,7 @@ catch (e_16) { | ||
} | ||
async exists(path, cred) { | ||
async exists(path) { | ||
const env_17 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_17, await this.lock(path), false); | ||
return await super.exists(path, cred); | ||
const _ = __addDisposableResource(env_17, await this.lock(path, 'exists'), false); | ||
return await super.exists(path); | ||
} | ||
@@ -416,7 +424,7 @@ catch (e_17) { | ||
} | ||
existsSync(path, cred) { | ||
existsSync(path) { | ||
const env_18 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_18, this.lockSync(path), false); | ||
return super.existsSync(path, cred); | ||
return super.existsSync(path); | ||
} | ||
@@ -431,8 +439,8 @@ catch (e_18) { | ||
} | ||
async link(srcpath, dstpath, cred) { | ||
async link(srcpath, dstpath) { | ||
const env_19 = { stack: [], error: void 0, hasError: false }; | ||
try { | ||
const _ = __addDisposableResource(env_19, await this.lock(srcpath), false); | ||
const _ = __addDisposableResource(env_19, await this.lock(srcpath, 'link'), false); | ||
// @ts-expect-error 2513 | ||
await super.link(srcpath, dstpath, cred); | ||
await super.link(srcpath, dstpath); | ||
} | ||
@@ -447,3 +455,3 @@ catch (e_19) { | ||
} | ||
linkSync(srcpath, dstpath, cred) { | ||
linkSync(srcpath, dstpath) { | ||
const env_20 = { stack: [], error: void 0, hasError: false }; | ||
@@ -453,3 +461,3 @@ try { | ||
// @ts-expect-error 2513 | ||
return super.linkSync(srcpath, dstpath, cred); | ||
return super.linkSync(srcpath, dstpath); | ||
} | ||
@@ -467,3 +475,3 @@ catch (e_20) { | ||
try { | ||
const _ = __addDisposableResource(env_21, await this.lock(path), false); | ||
const _ = __addDisposableResource(env_21, await this.lock(path, 'sync'), false); | ||
// @ts-expect-error 2513 | ||
@@ -470,0 +478,0 @@ await super.sync(path, data, stats); |
@@ -1,2 +0,1 @@ | ||
import type { Cred } from '../cred.js'; | ||
import type { File } from '../file.js'; | ||
@@ -11,16 +10,16 @@ import type { FileSystem, FileSystemMetadata } from '../filesystem.js'; | ||
metadata(): FileSystemMetadata; | ||
rename(oldPath: string, newPath: string, cred: Cred): Promise<void>; | ||
renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>; | ||
createFileSync(path: string, flag: string, mode: number, cred: Cred): File; | ||
unlink(path: string, cred: Cred): Promise<void>; | ||
unlinkSync(path: string, cred: Cred): void; | ||
rmdir(path: string, cred: Cred): Promise<void>; | ||
rmdirSync(path: string, cred: Cred): void; | ||
mkdir(path: string, mode: number, cred: Cred): Promise<void>; | ||
mkdirSync(path: string, mode: number, cred: Cred): void; | ||
link(srcpath: string, dstpath: string, cred: Cred): Promise<void>; | ||
linkSync(srcpath: string, dstpath: string, cred: Cred): void; | ||
rename(oldPath: string, newPath: string): Promise<void>; | ||
renameSync(oldPath: string, newPath: string): void; | ||
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; | ||
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; | ||
}>; |
@@ -12,36 +12,36 @@ import { Errno, ErrnoError } from '../error.js'; | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
async rename(oldPath, newPath, cred) { | ||
async rename(oldPath, newPath) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
renameSync(oldPath, newPath, cred) { | ||
renameSync(oldPath, newPath) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
async createFile(path, flag, mode, cred) { | ||
async createFile(path, flag, mode) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
createFileSync(path, flag, mode, cred) { | ||
createFileSync(path, flag, mode) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
async unlink(path, cred) { | ||
async unlink(path) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
unlinkSync(path, cred) { | ||
unlinkSync(path) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
async rmdir(path, cred) { | ||
async rmdir(path) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
rmdirSync(path, cred) { | ||
rmdirSync(path) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
async mkdir(path, mode, cred) { | ||
async mkdir(path, mode) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
mkdirSync(path, mode, cred) { | ||
mkdirSync(path, mode) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
async link(srcpath, dstpath, cred) { | ||
async link(srcpath, dstpath) { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
linkSync(srcpath, dstpath, cred) { | ||
linkSync(srcpath, dstpath) { | ||
throw new ErrnoError(Errno.EROFS); | ||
@@ -48,0 +48,0 @@ } |
@@ -7,31 +7,31 @@ /** | ||
class SyncFS extends FS { | ||
async exists(path, cred) { | ||
return this.existsSync(path, cred); | ||
async exists(path) { | ||
return this.existsSync(path); | ||
} | ||
async rename(oldPath, newPath, cred) { | ||
return this.renameSync(oldPath, newPath, cred); | ||
async rename(oldPath, newPath) { | ||
return this.renameSync(oldPath, newPath); | ||
} | ||
async stat(path, cred) { | ||
return this.statSync(path, cred); | ||
async stat(path) { | ||
return this.statSync(path); | ||
} | ||
async createFile(path, flag, mode, cred) { | ||
return this.createFileSync(path, flag, mode, cred); | ||
async createFile(path, flag, mode) { | ||
return this.createFileSync(path, flag, mode); | ||
} | ||
async openFile(path, flag, cred) { | ||
return this.openFileSync(path, flag, cred); | ||
async openFile(path, flag) { | ||
return this.openFileSync(path, flag); | ||
} | ||
async unlink(path, cred) { | ||
return this.unlinkSync(path, cred); | ||
async unlink(path) { | ||
return this.unlinkSync(path); | ||
} | ||
async rmdir(path, cred) { | ||
return this.rmdirSync(path, cred); | ||
async rmdir(path) { | ||
return this.rmdirSync(path); | ||
} | ||
async mkdir(path, mode, cred) { | ||
return this.mkdirSync(path, mode, cred); | ||
async mkdir(path, mode) { | ||
return this.mkdirSync(path, mode); | ||
} | ||
async readdir(path, cred) { | ||
return this.readdirSync(path, cred); | ||
async readdir(path) { | ||
return this.readdirSync(path); | ||
} | ||
async link(srcpath, dstpath, cred) { | ||
return this.linkSync(srcpath, dstpath, cred); | ||
async link(srcpath, dstpath) { | ||
return this.linkSync(srcpath, dstpath); | ||
} | ||
@@ -38,0 +38,0 @@ async sync(path, data, stats) { |
/// <reference types="node" resolution-mode="require"/> | ||
import type * as Node from 'fs'; | ||
import type { Cred } from './cred.js'; | ||
import type { Credentials } from './credentials.js'; | ||
import { S_IFDIR, S_IFLNK, S_IFREG } from './emulation/constants.js'; | ||
@@ -153,3 +153,3 @@ /** | ||
*/ | ||
hasAccess(mode: number, cred: Cred): boolean; | ||
hasAccess(mode: number, cred: Credentials): boolean; | ||
/** | ||
@@ -159,3 +159,3 @@ * Convert the current stats object into a credentials object | ||
*/ | ||
cred(uid?: number, gid?: number): Cred; | ||
cred(uid?: number, gid?: number): Credentials; | ||
/** | ||
@@ -190,4 +190,2 @@ * Change the mode of the file. We use this helper function to prevent messing | ||
* Stats with bigint | ||
* @todo Implement with bigint instead of wrapping Stats | ||
* @internal | ||
*/ | ||
@@ -198,4 +196,13 @@ export declare class BigIntStats extends StatsCommon<bigint> implements Node.BigIntStats, StatsLike { | ||
/** | ||
* Determines if the file stats have changed by comparing relevant properties. | ||
* | ||
* @param left The previous stats. | ||
* @param right The current stats. | ||
* @returns `true` if stats have changed; otherwise, `false`. | ||
* @internal | ||
*/ | ||
export declare function isStatsEqual<T extends number | bigint>(left: StatsCommon<T>, right: StatsCommon<T>): boolean; | ||
/** | ||
* @internal | ||
*/ | ||
export declare const ZenFsType = 525687744115; | ||
@@ -202,0 +209,0 @@ /** |
@@ -197,4 +197,2 @@ 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'; | ||
* Stats with bigint | ||
* @todo Implement with bigint instead of wrapping Stats | ||
* @internal | ||
*/ | ||
@@ -208,4 +206,15 @@ export class BigIntStats extends StatsCommon { | ||
/** | ||
* Determines if the file stats have changed by comparing relevant properties. | ||
* | ||
* @param left The previous stats. | ||
* @param right The current stats. | ||
* @returns `true` if stats have changed; otherwise, `false`. | ||
* @internal | ||
*/ | ||
export function isStatsEqual(left, right) { | ||
return left.size == right.size && +left.atime == +right.atime && +left.mtime == +right.mtime && +left.ctime == +right.ctime && left.mode == right.mode; | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
export const ZenFsType = 0x7a656e6673; // 'z' 'e' 'n' 'f' 's' | ||
@@ -212,0 +221,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 AbsolutePath } from './emulation/path.js'; | ||
import { ErrnoError } from './error.js'; | ||
import type { Cred } from './cred.js'; | ||
import { type AbsolutePath } from './emulation/path.js'; | ||
import type { FileSystem } from './filesystem.js'; | ||
import type * as fs from 'node:fs'; | ||
declare global { | ||
@@ -17,3 +16,3 @@ function atob(data: string): string; | ||
*/ | ||
export declare function mkdirpSync(path: string, mode: number, cred: Cred, fs: FileSystem): void; | ||
export declare function mkdirpSync(path: string, mode: number, fs: FileSystem): void; | ||
/** | ||
@@ -20,0 +19,0 @@ * Calculates levenshtein distance. |
@@ -1,3 +0,3 @@ | ||
import { ErrnoError, Errno } from './error.js'; | ||
import { dirname, resolve } from './emulation/path.js'; | ||
import { Errno, ErrnoError } from './error.js'; | ||
/** | ||
@@ -7,6 +7,6 @@ * Synchronous recursive makedir. | ||
*/ | ||
export function mkdirpSync(path, mode, cred, fs) { | ||
if (!fs.existsSync(path, cred)) { | ||
mkdirpSync(dirname(path), mode, cred, fs); | ||
fs.mkdirSync(path, mode, cred); | ||
export function mkdirpSync(path, mode, fs) { | ||
if (!fs.existsSync(path)) { | ||
mkdirpSync(dirname(path), mode, fs); | ||
fs.mkdirSync(path, mode); | ||
} | ||
@@ -118,8 +118,3 @@ } | ||
export function decodeDirListing(data) { | ||
return JSON.parse(decode(data), (k, v) => { | ||
if (k == '') { | ||
return v; | ||
} | ||
return BigInt(v); | ||
}); | ||
return JSON.parse(decode(data), (k, v) => (k == '' ? v : BigInt(v))); | ||
} | ||
@@ -131,8 +126,3 @@ /** | ||
export function encodeDirListing(data) { | ||
return encode(JSON.stringify(data, (k, v) => { | ||
if (k == '') { | ||
return v; | ||
} | ||
return v.toString(); | ||
})); | ||
return encode(JSON.stringify(data, (k, v) => (k == '' ? v : v.toString()))); | ||
} | ||
@@ -139,0 +129,0 @@ /** |
{ | ||
"name": "@zenfs/core", | ||
"version": "0.17.1", | ||
"version": "0.18.0", | ||
"description": "A filesystem, anywhere", | ||
@@ -68,3 +68,3 @@ "main": "dist/index.js", | ||
"readable-stream": "^4.5.2", | ||
"utilium": "^0.4.0" | ||
"utilium": ">=0.4.0" | ||
}, | ||
@@ -71,0 +71,0 @@ "devDependencies": { |
@@ -153,6 +153,5 @@ import type { RequiredKeys } from 'utilium'; | ||
* | ||
* Individual options can recursively contain BackendConfig objects for | ||
* option values that require file systems. | ||
* Individual options can recursively contain BackendConfiguration objects for values that require file systems. | ||
* | ||
* The option object for each file system corresponds to that file system's option object passed to its `Create()` method. | ||
* The configuration for each file system corresponds to that file system's option object passed to its `create()` method. | ||
*/ | ||
@@ -159,0 +158,0 @@ export type BackendConfiguration<T extends Backend> = OptionsOf<T> & Partial<SharedConfig> & { backend: T }; |
@@ -123,6 +123,6 @@ import { Errno, ErrnoError } from '../error.js'; | ||
if (!stats) { | ||
throw ErrnoError.With('ENOENT', path, 'preloadFile'); | ||
throw ErrnoError.With('ENOENT', path, 'preload'); | ||
} | ||
if (!stats.isFile()) { | ||
throw ErrnoError.With('EISDIR', path, 'preloadFile'); | ||
throw ErrnoError.With('EISDIR', path, 'preload'); | ||
} | ||
@@ -129,0 +129,0 @@ stats.size = buffer.length; |
/* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */ | ||
import { isJSON } from 'utilium'; | ||
import type { Cred } from '../cred.js'; | ||
import { basename, dirname } from '../emulation/path.js'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { NoSyncFile, flagToMode, isWriteable } from '../file.js'; | ||
import { NoSyncFile, isWriteable } from '../file.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
@@ -152,3 +151,3 @@ import { Readonly } from '../mixins/readonly.js'; | ||
public async openFile(path: string, flag: string, cred: Cred): Promise<NoSyncFile<this>> { | ||
public async openFile(path: string, flag: string): Promise<NoSyncFile<this>> { | ||
if (isWriteable(flag)) { | ||
@@ -166,10 +165,6 @@ // You can't write to files on this file system. | ||
if (!stats.hasAccess(flagToMode(flag), cred)) { | ||
throw ErrnoError.With('EACCES', path, 'openFile'); | ||
} | ||
return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : await this.getData(path, stats)); | ||
} | ||
public openFileSync(path: string, flag: string, cred: Cred): NoSyncFile<this> { | ||
public openFileSync(path: string, flag: string): NoSyncFile<this> { | ||
if (isWriteable(flag)) { | ||
@@ -187,6 +182,2 @@ // You can't write to files on this file system. | ||
if (!stats.hasAccess(flagToMode(flag), cred)) { | ||
throw ErrnoError.With('EACCES', path, 'openFile'); | ||
} | ||
return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : this.getDataSync(path, stats)); | ||
@@ -193,0 +184,0 @@ } |
@@ -0,11 +1,9 @@ | ||
import { dirname } from '../emulation/path.js'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import type { File } from '../file.js'; | ||
import { PreloadFile, parseFlag } from '../file.js'; | ||
import type { FileSystemMetadata } from '../filesystem.js'; | ||
import { FileSystem } from '../filesystem.js'; | ||
import { ErrnoError, Errno } from '../error.js'; | ||
import type { File } from '../file.js'; | ||
import { PreloadFile, parseFlag } from '../file.js'; | ||
import { Mutexed } from '../mixins/mutexed.js'; | ||
import { Stats } from '../stats.js'; | ||
import { Mutexed } from '../mixins/mutexed.js'; | ||
import { dirname } from '../emulation/path.js'; | ||
import type { Cred } from '../cred.js'; | ||
import { rootCred } from '../cred.js'; | ||
import { decode, encode } from '../utils.js'; | ||
@@ -80,6 +78,5 @@ import type { Backend } from './backend.js'; | ||
public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> { | ||
const cred = stats.cred(0, 0); | ||
await this.createParentDirectories(path, cred); | ||
if (!(await this.writable.exists(path, cred))) { | ||
await this.writable.createFile(path, 'w', 0o644, cred); | ||
await this.createParentDirectories(path); | ||
if (!(await this.writable.exists(path))) { | ||
await this.writable.createFile(path, 'w', 0o644); | ||
} | ||
@@ -90,4 +87,3 @@ await this.writable.sync(path, data, stats); | ||
public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void { | ||
const cred = stats.cred(0, 0); | ||
this.createParentDirectoriesSync(path, cred); | ||
this.createParentDirectoriesSync(path); | ||
this.writable.syncSync(path, data, stats); | ||
@@ -107,3 +103,3 @@ } | ||
try { | ||
const file = await this.writable.openFile(deletionLogPath, parseFlag('r'), rootCred); | ||
const file = await this.writable.openFile(deletionLogPath, parseFlag('r')); | ||
const { size } = await file.stat(); | ||
@@ -125,9 +121,9 @@ const { buffer } = await file.read(new Uint8Array(size)); | ||
public async restoreDeletionLog(log: string, cred: Cred): Promise<void> { | ||
public async restoreDeletionLog(log: string): Promise<void> { | ||
this._deleteLog = log; | ||
this._reparseDeletionLog(); | ||
await this.updateLog('', cred); | ||
await this.updateLog(''); | ||
} | ||
public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> { | ||
public async rename(oldPath: string, newPath: string): Promise<void> { | ||
this.checkInitialized(); | ||
@@ -138,3 +134,3 @@ this.checkPath(oldPath); | ||
try { | ||
await this.writable.rename(oldPath, newPath, cred); | ||
await this.writable.rename(oldPath, newPath); | ||
} catch (e) { | ||
@@ -147,3 +143,3 @@ if (this._deletedFiles.has(oldPath)) { | ||
public renameSync(oldPath: string, newPath: string, cred: Cred): void { | ||
public renameSync(oldPath: string, newPath: string): void { | ||
this.checkInitialized(); | ||
@@ -154,3 +150,3 @@ this.checkPath(oldPath); | ||
try { | ||
this.writable.renameSync(oldPath, newPath, cred); | ||
this.writable.renameSync(oldPath, newPath); | ||
} catch (e) { | ||
@@ -163,6 +159,6 @@ if (this._deletedFiles.has(oldPath)) { | ||
public async stat(path: string, cred: Cred): Promise<Stats> { | ||
public async stat(path: string): Promise<Stats> { | ||
this.checkInitialized(); | ||
try { | ||
return await this.writable.stat(path, cred); | ||
return await this.writable.stat(path); | ||
} catch (e) { | ||
@@ -172,3 +168,3 @@ if (this._deletedFiles.has(path)) { | ||
} | ||
const oldStat = new Stats(await this.readable.stat(path, cred)); | ||
const oldStat = new Stats(await this.readable.stat(path)); | ||
// Make the oldStat's mode writable. Preserve the topmost part of the mode, which specifies the type | ||
@@ -180,6 +176,6 @@ oldStat.mode |= 0o222; | ||
public statSync(path: string, cred: Cred): Stats { | ||
public statSync(path: string): Stats { | ||
this.checkInitialized(); | ||
try { | ||
return this.writable.statSync(path, cred); | ||
return this.writable.statSync(path); | ||
} catch (e) { | ||
@@ -189,3 +185,3 @@ if (this._deletedFiles.has(path)) { | ||
} | ||
const oldStat = new Stats(this.readable.statSync(path, cred)); | ||
const oldStat = new Stats(this.readable.statSync(path)); | ||
// Make the oldStat's mode writable. Preserve the topmost part of the mode, which specifies the type. | ||
@@ -197,8 +193,8 @@ oldStat.mode |= 0o222; | ||
public async openFile(path: string, flag: string, cred: Cred): Promise<File> { | ||
if (await this.writable.exists(path, cred)) { | ||
return this.writable.openFile(path, flag, cred); | ||
public async openFile(path: string, flag: string): Promise<File> { | ||
if (await this.writable.exists(path)) { | ||
return this.writable.openFile(path, flag); | ||
} | ||
// Create an OverlayFile. | ||
const file = await this.readable.openFile(path, parseFlag('r'), cred); | ||
const file = await this.readable.openFile(path, parseFlag('r')); | ||
const stats = new Stats(await file.stat()); | ||
@@ -209,8 +205,8 @@ const { buffer } = await file.read(new Uint8Array(stats.size)); | ||
public openFileSync(path: string, flag: string, cred: Cred): File { | ||
if (this.writable.existsSync(path, cred)) { | ||
return this.writable.openFileSync(path, flag, cred); | ||
public openFileSync(path: string, flag: string): File { | ||
if (this.writable.existsSync(path)) { | ||
return this.writable.openFileSync(path, flag); | ||
} | ||
// Create an OverlayFile. | ||
const file = this.readable.openFileSync(path, parseFlag('r'), cred); | ||
const file = this.readable.openFileSync(path, parseFlag('r')); | ||
const stats = new Stats(file.statSync()); | ||
@@ -222,72 +218,72 @@ const data = new Uint8Array(stats.size); | ||
public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> { | ||
public async createFile(path: string, flag: string, mode: number): Promise<File> { | ||
this.checkInitialized(); | ||
await this.writable.createFile(path, flag, mode, cred); | ||
return this.openFile(path, flag, cred); | ||
await this.writable.createFile(path, flag, mode); | ||
return this.openFile(path, flag); | ||
} | ||
public createFileSync(path: string, flag: string, mode: number, cred: Cred): File { | ||
public createFileSync(path: string, flag: string, mode: number): File { | ||
this.checkInitialized(); | ||
this.writable.createFileSync(path, flag, mode, cred); | ||
return this.openFileSync(path, flag, cred); | ||
this.writable.createFileSync(path, flag, mode); | ||
return this.openFileSync(path, flag); | ||
} | ||
public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> { | ||
public async link(srcpath: string, dstpath: string): Promise<void> { | ||
this.checkInitialized(); | ||
await this.writable.link(srcpath, dstpath, cred); | ||
await this.writable.link(srcpath, dstpath); | ||
} | ||
public linkSync(srcpath: string, dstpath: string, cred: Cred): void { | ||
public linkSync(srcpath: string, dstpath: string): void { | ||
this.checkInitialized(); | ||
this.writable.linkSync(srcpath, dstpath, cred); | ||
this.writable.linkSync(srcpath, dstpath); | ||
} | ||
public async unlink(path: string, cred: Cred): Promise<void> { | ||
public async unlink(path: string): Promise<void> { | ||
this.checkInitialized(); | ||
this.checkPath(path); | ||
if (!(await this.exists(path, cred))) { | ||
if (!(await this.exists(path))) { | ||
throw ErrnoError.With('ENOENT', path, 'unlink'); | ||
} | ||
if (await this.writable.exists(path, cred)) { | ||
await this.writable.unlink(path, cred); | ||
if (await this.writable.exists(path)) { | ||
await this.writable.unlink(path); | ||
} | ||
// if it still exists add to the delete log | ||
if (await this.exists(path, cred)) { | ||
await this.deletePath(path, cred); | ||
if (await this.exists(path)) { | ||
await this.deletePath(path); | ||
} | ||
} | ||
public unlinkSync(path: string, cred: Cred): void { | ||
public unlinkSync(path: string): void { | ||
this.checkInitialized(); | ||
this.checkPath(path); | ||
if (!this.existsSync(path, cred)) { | ||
if (!this.existsSync(path)) { | ||
throw ErrnoError.With('ENOENT', path, 'unlink'); | ||
} | ||
if (this.writable.existsSync(path, cred)) { | ||
this.writable.unlinkSync(path, cred); | ||
if (this.writable.existsSync(path)) { | ||
this.writable.unlinkSync(path); | ||
} | ||
// if it still exists add to the delete log | ||
if (this.existsSync(path, cred)) { | ||
void this.deletePath(path, cred); | ||
if (this.existsSync(path)) { | ||
void this.deletePath(path); | ||
} | ||
} | ||
public async rmdir(path: string, cred: Cred): Promise<void> { | ||
public async rmdir(path: string): Promise<void> { | ||
this.checkInitialized(); | ||
if (!(await this.exists(path, cred))) { | ||
if (!(await this.exists(path))) { | ||
throw ErrnoError.With('ENOENT', path, 'rmdir'); | ||
} | ||
if (await this.writable.exists(path, cred)) { | ||
await this.writable.rmdir(path, cred); | ||
if (await this.writable.exists(path)) { | ||
await this.writable.rmdir(path); | ||
} | ||
if (await this.exists(path, cred)) { | ||
if (await this.exists(path)) { | ||
// Check if directory is empty. | ||
if ((await this.readdir(path, cred)).length > 0) { | ||
if ((await this.readdir(path)).length > 0) { | ||
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir'); | ||
} else { | ||
await this.deletePath(path, cred); | ||
await this.deletePath(path); | ||
} | ||
@@ -297,16 +293,16 @@ } | ||
public rmdirSync(path: string, cred: Cred): void { | ||
public rmdirSync(path: string): void { | ||
this.checkInitialized(); | ||
if (!this.existsSync(path, cred)) { | ||
if (!this.existsSync(path)) { | ||
throw ErrnoError.With('ENOENT', path, 'rmdir'); | ||
} | ||
if (this.writable.existsSync(path, cred)) { | ||
this.writable.rmdirSync(path, cred); | ||
if (this.writable.existsSync(path)) { | ||
this.writable.rmdirSync(path); | ||
} | ||
if (this.existsSync(path, cred)) { | ||
if (this.existsSync(path)) { | ||
// Check if directory is empty. | ||
if (this.readdirSync(path, cred).length > 0) { | ||
if (this.readdirSync(path).length > 0) { | ||
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir'); | ||
} else { | ||
void this.deletePath(path, cred); | ||
void this.deletePath(path); | ||
} | ||
@@ -316,25 +312,25 @@ } | ||
public async mkdir(path: string, mode: number, cred: Cred): Promise<void> { | ||
public async mkdir(path: string, mode: number): Promise<void> { | ||
this.checkInitialized(); | ||
if (await this.exists(path, cred)) { | ||
if (await this.exists(path)) { | ||
throw ErrnoError.With('EEXIST', path, 'mkdir'); | ||
} | ||
// The below will throw should any of the parent directories fail to exist on _writable. | ||
await this.createParentDirectories(path, cred); | ||
await this.writable.mkdir(path, mode, cred); | ||
await this.createParentDirectories(path); | ||
await this.writable.mkdir(path, mode); | ||
} | ||
public mkdirSync(path: string, mode: number, cred: Cred): void { | ||
public mkdirSync(path: string, mode: number): void { | ||
this.checkInitialized(); | ||
if (this.existsSync(path, cred)) { | ||
if (this.existsSync(path)) { | ||
throw ErrnoError.With('EEXIST', path, 'mkdir'); | ||
} | ||
// The below will throw should any of the parent directories fail to exist on _writable. | ||
this.createParentDirectoriesSync(path, cred); | ||
this.writable.mkdirSync(path, mode, cred); | ||
this.createParentDirectoriesSync(path); | ||
this.writable.mkdirSync(path, mode); | ||
} | ||
public async readdir(path: string, cred: Cred): Promise<string[]> { | ||
public async readdir(path: string): Promise<string[]> { | ||
this.checkInitialized(); | ||
const dirStats = await this.stat(path, cred); | ||
const dirStats = await this.stat(path); | ||
if (!dirStats.isDirectory()) { | ||
@@ -347,3 +343,3 @@ throw ErrnoError.With('ENOTDIR', path, 'readdir'); | ||
try { | ||
contents.push(...(await this.writable.readdir(path, cred))); | ||
contents.push(...(await this.writable.readdir(path))); | ||
} catch (e) { | ||
@@ -353,3 +349,3 @@ // NOP. | ||
try { | ||
contents.push(...(await this.readable.readdir(path, cred)).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`))); | ||
contents.push(...(await this.readable.readdir(path)).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`))); | ||
} catch (e) { | ||
@@ -366,5 +362,5 @@ // NOP. | ||
public readdirSync(path: string, cred: Cred): string[] { | ||
public readdirSync(path: string): string[] { | ||
this.checkInitialized(); | ||
const dirStats = this.statSync(path, cred); | ||
const dirStats = this.statSync(path); | ||
if (!dirStats.isDirectory()) { | ||
@@ -377,3 +373,3 @@ throw ErrnoError.With('ENOTDIR', path, 'readdir'); | ||
try { | ||
contents = contents.concat(this.writable.readdirSync(path, cred)); | ||
contents = contents.concat(this.writable.readdirSync(path)); | ||
} catch (e) { | ||
@@ -383,3 +379,3 @@ // NOP. | ||
try { | ||
contents = contents.concat(this.readable.readdirSync(path, cred).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`))); | ||
contents = contents.concat(this.readable.readdirSync(path).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`))); | ||
} catch (e) { | ||
@@ -396,8 +392,8 @@ // NOP. | ||
private async deletePath(path: string, cred: Cred): Promise<void> { | ||
private async deletePath(path: string): Promise<void> { | ||
this._deletedFiles.add(path); | ||
await this.updateLog(`d${path}\n`, cred); | ||
await this.updateLog(`d${path}\n`); | ||
} | ||
private async updateLog(addition: string, cred: Cred) { | ||
private async updateLog(addition: string) { | ||
this._deleteLog += addition; | ||
@@ -409,3 +405,3 @@ if (this._deleteLogUpdatePending) { | ||
this._deleteLogUpdatePending = true; | ||
const log = await this.writable.openFile(deletionLogPath, parseFlag('w'), cred); | ||
const log = await this.writable.openFile(deletionLogPath, parseFlag('w')); | ||
try { | ||
@@ -415,3 +411,3 @@ await log.write(encode(this._deleteLog)); | ||
this._deleteLogUpdateNeeded = false; | ||
await this.updateLog('', cred); | ||
await this.updateLog(''); | ||
} | ||
@@ -462,6 +458,6 @@ } catch (e) { | ||
*/ | ||
private createParentDirectoriesSync(path: string, cred: Cred): void { | ||
private createParentDirectoriesSync(path: string): void { | ||
let parent = dirname(path), | ||
toCreate: string[] = []; | ||
while (!this.writable.existsSync(parent, cred)) { | ||
while (!this.writable.existsSync(parent)) { | ||
toCreate.push(parent); | ||
@@ -473,10 +469,10 @@ parent = dirname(parent); | ||
for (const p of toCreate) { | ||
this.writable.mkdirSync(p, this.statSync(p, cred).mode, cred); | ||
this.writable.mkdirSync(p, this.statSync(p).mode); | ||
} | ||
} | ||
private async createParentDirectories(path: string, cred: Cred): Promise<void> { | ||
private async createParentDirectories(path: string): Promise<void> { | ||
let parent = dirname(path), | ||
toCreate: string[] = []; | ||
while (!(await this.writable.exists(parent, cred))) { | ||
while (!(await this.writable.exists(parent))) { | ||
toCreate.push(parent); | ||
@@ -488,4 +484,4 @@ parent = dirname(parent); | ||
for (const p of toCreate) { | ||
const stats = await this.stat(p, cred); | ||
await this.writable.mkdir(p, stats.mode, cred); | ||
const stats = await this.stat(p); | ||
await this.writable.mkdir(p, stats.mode); | ||
} | ||
@@ -499,20 +495,20 @@ } | ||
*/ | ||
private operateOnWritable(path: string, cred: Cred): void { | ||
if (!this.existsSync(path, cred)) { | ||
private operateOnWritable(path: string): void { | ||
if (!this.existsSync(path)) { | ||
throw ErrnoError.With('ENOENT', path, 'operateOnWriteable'); | ||
} | ||
if (!this.writable.existsSync(path, cred)) { | ||
if (!this.writable.existsSync(path)) { | ||
// File is on readable storage. Copy to writable storage before | ||
// changing its mode. | ||
this.copyToWritableSync(path, cred); | ||
this.copyToWritableSync(path); | ||
} | ||
} | ||
private async operateOnWritableAsync(path: string, cred: Cred): Promise<void> { | ||
if (!(await this.exists(path, cred))) { | ||
private async operateOnWritableAsync(path: string): Promise<void> { | ||
if (!(await this.exists(path))) { | ||
throw ErrnoError.With('ENOENT', path, 'operateOnWritable'); | ||
} | ||
if (!(await this.writable.exists(path, cred))) { | ||
return this.copyToWritable(path, cred); | ||
if (!(await this.writable.exists(path))) { | ||
return this.copyToWritable(path); | ||
} | ||
@@ -525,6 +521,6 @@ } | ||
*/ | ||
private copyToWritableSync(path: string, cred: Cred): void { | ||
const stats = this.statSync(path, cred); | ||
private copyToWritableSync(path: string): void { | ||
const stats = this.statSync(path); | ||
if (stats.isDirectory()) { | ||
this.writable.mkdirSync(path, stats.mode, cred); | ||
this.writable.mkdirSync(path, stats.mode); | ||
return; | ||
@@ -534,6 +530,6 @@ } | ||
const data = new Uint8Array(stats.size); | ||
const readable = this.readable.openFileSync(path, parseFlag('r'), cred); | ||
const readable = this.readable.openFileSync(path, parseFlag('r')); | ||
readable.readSync(data); | ||
readable.closeSync(); | ||
const writable = this.writable.openFileSync(path, parseFlag('w'), cred); | ||
const writable = this.writable.openFileSync(path, parseFlag('w')); | ||
writable.writeSync(data); | ||
@@ -543,6 +539,6 @@ writable.closeSync(); | ||
private async copyToWritable(path: string, cred: Cred): Promise<void> { | ||
const stats = await this.stat(path, cred); | ||
private async copyToWritable(path: string): Promise<void> { | ||
const stats = await this.stat(path); | ||
if (stats.isDirectory()) { | ||
await this.writable.mkdir(path, stats.mode, cred); | ||
await this.writable.mkdir(path, stats.mode); | ||
return; | ||
@@ -552,6 +548,6 @@ } | ||
const data = new Uint8Array(stats.size); | ||
const readable = await this.readable.openFile(path, parseFlag('r'), cred); | ||
const readable = await this.readable.openFile(path, parseFlag('r')); | ||
await readable.read(data); | ||
await readable.close(); | ||
const writable = await this.writable.openFile(path, parseFlag('w'), cred); | ||
const writable = await this.writable.openFile(path, parseFlag('w')); | ||
await writable.write(data); | ||
@@ -558,0 +554,0 @@ await writable.close(); |
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import type { FileReadResult } from 'node:fs/promises'; | ||
import type { ExtractProperties } from 'utilium'; | ||
import type { Cred } from '../../cred.js'; | ||
import { resolveMountConfig, type MountConfiguration } from '../../config.js'; | ||
import { Errno, ErrnoError } from '../../error.js'; | ||
@@ -10,6 +10,5 @@ import { File } from '../../file.js'; | ||
import { Stats, type FileType } from '../../stats.js'; | ||
import type { Backend, FilesystemOf } from '../backend.js'; | ||
import { InMemory } from '../memory.js'; | ||
import type { Backend, FilesystemOf } from '../backend.js'; | ||
import * as RPC from './rpc.js'; | ||
import { type MountConfiguration, resolveMountConfig } from '../../config.js'; | ||
@@ -35,3 +34,3 @@ type FileMethods = Omit<ExtractProperties<File, (...args: any[]) => Promise<any>>, typeof Symbol.asyncDispose>; | ||
public rpc<const T extends FileMethod & string>(method: T, ...args: Parameters<FileMethods[T]>): Promise<Awaited<ReturnType<FileMethods[T]>>> { | ||
public rpc<const T extends FileMethod>(method: T, ...args: Parameters<FileMethods[T]>): Promise<Awaited<ReturnType<FileMethods[T]>>> { | ||
return RPC.request<FileRequest<T>, Awaited<ReturnType<FileMethods[T]>>>( | ||
@@ -52,4 +51,4 @@ { | ||
public stat(): Promise<Stats> { | ||
return this.rpc('stat'); | ||
public async stat(): Promise<Stats> { | ||
return new Stats(await this.rpc('stat')); | ||
} | ||
@@ -190,8 +189,8 @@ | ||
public rename(oldPath: string, newPath: string, cred: Cred): Promise<void> { | ||
return this.rpc('rename', oldPath, newPath, cred); | ||
public rename(oldPath: string, newPath: string): Promise<void> { | ||
return this.rpc('rename', oldPath, newPath); | ||
} | ||
public async stat(path: string, cred: Cred): Promise<Stats> { | ||
return new Stats(await this.rpc('stat', path, cred)); | ||
public async stat(path: string): Promise<Stats> { | ||
return new Stats(await this.rpc('stat', path)); | ||
} | ||
@@ -202,25 +201,25 @@ | ||
} | ||
public openFile(path: string, flag: string, cred: Cred): Promise<File> { | ||
return this.rpc('openFile', path, flag, cred); | ||
public openFile(path: string, flag: string): Promise<File> { | ||
return this.rpc('openFile', path, flag); | ||
} | ||
public createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> { | ||
return this.rpc('createFile', path, flag, mode, cred); | ||
public createFile(path: string, flag: string, mode: number): Promise<File> { | ||
return this.rpc('createFile', path, flag, mode); | ||
} | ||
public unlink(path: string, cred: Cred): Promise<void> { | ||
return this.rpc('unlink', path, cred); | ||
public unlink(path: string): Promise<void> { | ||
return this.rpc('unlink', path); | ||
} | ||
public rmdir(path: string, cred: Cred): Promise<void> { | ||
return this.rpc('rmdir', path, cred); | ||
public rmdir(path: string): Promise<void> { | ||
return this.rpc('rmdir', path); | ||
} | ||
public mkdir(path: string, mode: number, cred: Cred): Promise<void> { | ||
return this.rpc('mkdir', path, mode, cred); | ||
public mkdir(path: string, mode: number): Promise<void> { | ||
return this.rpc('mkdir', path, mode); | ||
} | ||
public readdir(path: string, cred: Cred): Promise<string[]> { | ||
return this.rpc('readdir', path, cred); | ||
public readdir(path: string): Promise<string[]> { | ||
return this.rpc('readdir', path); | ||
} | ||
public exists(path: string, cred: Cred): Promise<boolean> { | ||
return this.rpc('exists', path, cred); | ||
public exists(path: string): Promise<boolean> { | ||
return this.rpc('exists', path); | ||
} | ||
public link(srcpath: string, dstpath: string, cred: Cred): Promise<void> { | ||
return this.rpc('link', srcpath, dstpath, cred); | ||
public link(srcpath: string, dstpath: string): Promise<void> { | ||
return this.rpc('link', srcpath, dstpath); | ||
} | ||
@@ -227,0 +226,0 @@ } |
@@ -1,6 +0,6 @@ | ||
import type { Cred } from '../../cred.js'; | ||
import { R_OK, S_IFDIR, S_IFREG, W_OK } from '../../emulation/constants.js'; | ||
import { credentials } from '../../credentials.js'; | ||
import { S_IFDIR, S_IFREG } from '../../emulation/constants.js'; | ||
import { basename, dirname, join, resolve } from '../../emulation/path.js'; | ||
import { Errno, ErrnoError } from '../../error.js'; | ||
import { PreloadFile, flagToMode } from '../../file.js'; | ||
import { PreloadFile } from '../../file.js'; | ||
import { FileSystem, type FileSystemMetadata } from '../../filesystem.js'; | ||
@@ -67,3 +67,3 @@ import { type Ino, Inode, randomIno, rootIno } from '../../inode.js'; | ||
*/ | ||
public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> { | ||
public async rename(oldPath: string, newPath: string): Promise<void> { | ||
await using tx = this.store.transaction(); | ||
@@ -78,6 +78,2 @@ const oldParent = dirname(oldPath), | ||
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
} | ||
if (!oldDirList[oldName]) { | ||
@@ -110,11 +106,9 @@ throw ErrnoError.With('ENOENT', oldPath, 'rename'); | ||
if (newDirList[newName]) { | ||
// If it's a file, delete it. | ||
// If it's a file, delete it, if it's a directory, throw a permissions error. | ||
const newNameNode = await this.getINode(tx, newDirList[newName], newPath); | ||
if (newNameNode.toStats().isFile()) { | ||
await tx.remove(newNameNode.ino); | ||
await tx.remove(newDirList[newName]); | ||
} else { | ||
// If it's a directory, throw a permissions error. | ||
if (!newNameNode.toStats().isFile()) { | ||
throw ErrnoError.With('EPERM', newPath, 'rename'); | ||
} | ||
await tx.remove(newNameNode.ino); | ||
await tx.remove(newDirList[newName]); | ||
} | ||
@@ -128,3 +122,3 @@ newDirList[newName] = nodeId; | ||
public renameSync(oldPath: string, newPath: string, cred: Cred): void { | ||
public renameSync(oldPath: string, newPath: string): void { | ||
using tx = this.store.transaction(); | ||
@@ -139,6 +133,2 @@ const oldParent = dirname(oldPath), | ||
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
} | ||
if (!oldDirList[oldName]) { | ||
@@ -171,11 +161,9 @@ throw ErrnoError.With('ENOENT', oldPath, 'rename'); | ||
if (newDirList[newName]) { | ||
// If it's a file, delete it. | ||
// If it's a file, delete it, if it's a directory, throw a permissions error. | ||
const newNameNode = this.getINodeSync(tx, newDirList[newName], newPath); | ||
if (newNameNode.toStats().isFile()) { | ||
tx.removeSync(newNameNode.ino); | ||
tx.removeSync(newDirList[newName]); | ||
} else { | ||
// If it's a directory, throw a permissions error. | ||
if (!newNameNode.toStats().isFile()) { | ||
throw ErrnoError.With('EPERM', newPath, 'rename'); | ||
} | ||
tx.removeSync(newNameNode.ino); | ||
tx.removeSync(newDirList[newName]); | ||
} | ||
@@ -190,3 +178,3 @@ newDirList[newName] = ino; | ||
public async stat(path: string, cred: Cred): Promise<Stats> { | ||
public async stat(path: string): Promise<Stats> { | ||
await using tx = this.store.transaction(); | ||
@@ -197,36 +185,25 @@ const inode = await this.findINode(tx, path); | ||
} | ||
const stats = inode.toStats(); | ||
if (!stats.hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
} | ||
return stats; | ||
return inode.toStats(); | ||
} | ||
public statSync(path: string, cred: Cred): Stats { | ||
public statSync(path: string): Stats { | ||
using tx = this.store.transaction(); | ||
// Get the inode to the item, convert it into a Stats object. | ||
const stats = this.findINodeSync(tx, path).toStats(); | ||
if (!stats.hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
} | ||
return stats; | ||
return this.findINodeSync(tx, path).toStats(); | ||
} | ||
public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<PreloadFile<this>> { | ||
const node = await this.commitNew(path, S_IFREG, mode, cred, new Uint8Array(0)); | ||
public async createFile(path: string, flag: string, mode: number): Promise<PreloadFile<this>> { | ||
const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(0)); | ||
return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array(0)); | ||
} | ||
public createFileSync(path: string, flag: string, mode: number, cred: Cred): PreloadFile<this> { | ||
this.commitNewSync(path, S_IFREG, mode, cred); | ||
return this.openFileSync(path, flag, cred); | ||
public createFileSync(path: string, flag: string, mode: number): PreloadFile<this> { | ||
this.commitNewSync(path, S_IFREG, mode); | ||
return this.openFileSync(path, flag); | ||
} | ||
public async openFile(path: string, flag: string, cred: Cred): Promise<PreloadFile<this>> { | ||
public async openFile(path: string, flag: string): Promise<PreloadFile<this>> { | ||
await using tx = this.store.transaction(); | ||
const node = await this.findINode(tx, path), | ||
data = await tx.get(node.ino); | ||
if (!node.toStats().hasAccess(flagToMode(flag), cred)) { | ||
throw ErrnoError.With('EACCES', path, 'openFile'); | ||
} | ||
if (!data) { | ||
@@ -238,9 +215,6 @@ throw ErrnoError.With('ENOENT', path, 'openFile'); | ||
public openFileSync(path: string, flag: string, cred: Cred): PreloadFile<this> { | ||
public openFileSync(path: string, flag: string): PreloadFile<this> { | ||
using tx = this.store.transaction(); | ||
const node = this.findINodeSync(tx, path), | ||
data = tx.getSync(node.ino); | ||
if (!node.toStats().hasAccess(flagToMode(flag), cred)) { | ||
throw ErrnoError.With('EACCES', path, 'openFile'); | ||
} | ||
if (!data) { | ||
@@ -252,51 +226,43 @@ throw ErrnoError.With('ENOENT', path, 'openFile'); | ||
public async unlink(path: string, cred: Cred): Promise<void> { | ||
return this.remove(path, false, cred); | ||
public async unlink(path: string): Promise<void> { | ||
return this.remove(path, false); | ||
} | ||
public unlinkSync(path: string, cred: Cred): void { | ||
this.removeSync(path, false, cred); | ||
public unlinkSync(path: string): void { | ||
this.removeSync(path, false); | ||
} | ||
public async rmdir(path: string, cred: Cred): Promise<void> { | ||
public async rmdir(path: string): Promise<void> { | ||
// Check first if directory is empty. | ||
const list = await this.readdir(path, cred); | ||
if (list.length > 0) { | ||
if ((await this.readdir(path)).length) { | ||
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir'); | ||
} | ||
await this.remove(path, true, cred); | ||
await this.remove(path, true); | ||
} | ||
public rmdirSync(path: string, cred: Cred): void { | ||
public rmdirSync(path: string): void { | ||
// Check first if directory is empty. | ||
if (this.readdirSync(path, cred).length > 0) { | ||
if (this.readdirSync(path).length) { | ||
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir'); | ||
} else { | ||
this.removeSync(path, true, cred); | ||
} | ||
this.removeSync(path, true); | ||
} | ||
public async mkdir(path: string, mode: number, cred: Cred): Promise<void> { | ||
await this.commitNew(path, S_IFDIR, mode, cred, encode('{}')); | ||
public async mkdir(path: string, mode: number): Promise<void> { | ||
await this.commitNew(path, S_IFDIR, mode, encode('{}')); | ||
} | ||
public mkdirSync(path: string, mode: number, cred: Cred): void { | ||
this.commitNewSync(path, S_IFDIR, mode, cred, encode('{}')); | ||
public mkdirSync(path: string, mode: number): void { | ||
this.commitNewSync(path, S_IFDIR, mode, encode('{}')); | ||
} | ||
public async readdir(path: string, cred: Cred): Promise<string[]> { | ||
public async readdir(path: string): Promise<string[]> { | ||
await using tx = this.store.transaction(); | ||
const node = await this.findINode(tx, path); | ||
if (!node.toStats().hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'readdur'); | ||
} | ||
return Object.keys(await this.getDirListing(tx, node, path)); | ||
} | ||
public readdirSync(path: string, cred: Cred): string[] { | ||
public readdirSync(path: string): string[] { | ||
using tx = this.store.transaction(); | ||
const node = this.findINodeSync(tx, path); | ||
if (!node.toStats().hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
} | ||
return Object.keys(this.getDirListingSync(tx, node, path)); | ||
@@ -347,62 +313,35 @@ } | ||
public async link(existing: string, newpath: string, cred: Cred): Promise<void> { | ||
public async link(target: string, link: string): Promise<void> { | ||
await using tx = this.store.transaction(); | ||
const existingDir: string = dirname(existing), | ||
existingDirNode = await this.findINode(tx, existingDir); | ||
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', existingDir, 'link'); | ||
} | ||
const newDir: string = dirname(newpath), | ||
const newDir: string = dirname(link), | ||
newDirNode = await this.findINode(tx, newDir), | ||
newListing = await this.getDirListing(tx, newDirNode, newDir); | ||
listing = await this.getDirListing(tx, newDirNode, newDir); | ||
if (!newDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', newDir, 'link'); | ||
} | ||
const ino = await this._findINode(tx, dirname(target), basename(target)); | ||
const node = await this.getINode(tx, ino, target); | ||
const ino = await this._findINode(tx, existingDir, basename(existing)); | ||
const node = await this.getINode(tx, ino, existing); | ||
if (!node.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', newpath, 'link'); | ||
} | ||
node.nlink++; | ||
newListing[basename(newpath)] = ino; | ||
listing[basename(link)] = ino; | ||
tx.setSync(ino, node.data); | ||
tx.setSync(newDirNode.ino, encodeDirListing(newListing)); | ||
tx.setSync(newDirNode.ino, encodeDirListing(listing)); | ||
tx.commitSync(); | ||
} | ||
public linkSync(existing: string, newpath: string, cred: Cred): void { | ||
public linkSync(target: string, link: string): void { | ||
using tx = this.store.transaction(); | ||
const existingDir: string = dirname(existing), | ||
existingDirNode = this.findINodeSync(tx, existingDir); | ||
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) { | ||
throw ErrnoError.With('EACCES', existingDir, 'link'); | ||
} | ||
const newDir: string = dirname(newpath), | ||
const newDir: string = dirname(link), | ||
newDirNode = this.findINodeSync(tx, newDir), | ||
newListing = this.getDirListingSync(tx, newDirNode, newDir); | ||
listing = this.getDirListingSync(tx, newDirNode, newDir); | ||
if (!newDirNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', newDir, 'link'); | ||
} | ||
const ino = this._findINodeSync(tx, dirname(target), basename(target)); | ||
const node = this.getINodeSync(tx, ino, target); | ||
const ino = this._findINodeSync(tx, existingDir, basename(existing)); | ||
const node = this.getINodeSync(tx, ino, existing); | ||
if (!node.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', newpath, 'link'); | ||
} | ||
node.nlink++; | ||
newListing[basename(newpath)] = ino; | ||
listing[basename(link)] = ino; | ||
tx.setSync(ino, node.data); | ||
tx.setSync(newDirNode.ino, encodeDirListing(newListing)); | ||
tx.setSync(newDirNode.ino, encodeDirListing(listing)); | ||
tx.commitSync(); | ||
@@ -630,3 +569,3 @@ } | ||
*/ | ||
private async commitNew(path: string, type: FileType, mode: number, cred: Cred, data: Uint8Array): Promise<Inode> { | ||
private async commitNew(path: string, type: FileType, mode: number, data: Uint8Array): Promise<Inode> { | ||
await using tx = this.store.transaction(); | ||
@@ -636,7 +575,2 @@ const parentPath = dirname(path), | ||
//Check that the creater has correct access | ||
if (!parent.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'commitNewFile'); | ||
} | ||
const fname = basename(path), | ||
@@ -651,3 +585,3 @@ listing = await this.getDirListing(tx, parent, parentPath); | ||
if (path === '/') { | ||
throw ErrnoError.With('EEXIST', path, 'commitNewFile'); | ||
throw ErrnoError.With('EEXIST', path, 'commitNew'); | ||
} | ||
@@ -658,3 +592,3 @@ | ||
await tx.abort(); | ||
throw ErrnoError.With('EEXIST', path, 'commitNewFile'); | ||
throw ErrnoError.With('EEXIST', path, 'commitNew'); | ||
} | ||
@@ -666,4 +600,4 @@ | ||
inode.mode = mode | type; | ||
inode.uid = cred.uid; | ||
inode.gid = cred.gid; | ||
inode.uid = credentials.uid; | ||
inode.gid = credentials.gid; | ||
inode.size = data.length; | ||
@@ -687,3 +621,3 @@ | ||
*/ | ||
protected commitNewSync(path: string, type: FileType, mode: number, cred: Cred, data: Uint8Array = new Uint8Array()): Inode { | ||
protected commitNewSync(path: string, type: FileType, mode: number, data: Uint8Array = new Uint8Array()): Inode { | ||
using tx = this.store.transaction(); | ||
@@ -693,7 +627,2 @@ const parentPath = dirname(path), | ||
//Check that the creater has correct access | ||
if (!parent.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'commitNewFile'); | ||
} | ||
const fname = basename(path), | ||
@@ -708,3 +637,3 @@ listing = this.getDirListingSync(tx, parent, parentPath); | ||
if (path === '/') { | ||
throw ErrnoError.With('EEXIST', path, 'commitNewFile'); | ||
throw ErrnoError.With('EEXIST', path, 'commitNew'); | ||
} | ||
@@ -714,3 +643,3 @@ | ||
if (listing[fname]) { | ||
throw ErrnoError.With('EEXIST', path, 'commitNewFile'); | ||
throw ErrnoError.With('EEXIST', path, 'commitNew'); | ||
} | ||
@@ -723,4 +652,4 @@ | ||
node.mode = mode | type; | ||
node.uid = cred.uid; | ||
node.gid = cred.gid; | ||
node.uid = credentials.uid; | ||
node.gid = credentials.gid; | ||
// Update and commit parent directory listing. | ||
@@ -739,3 +668,3 @@ listing[fname] = this.addNewSync(tx, node.data, path); | ||
*/ | ||
private async remove(path: string, isDir: boolean, cred: Cred): Promise<void> { | ||
private async remove(path: string, isDir: boolean): Promise<void> { | ||
await using tx = this.store.transaction(); | ||
@@ -748,3 +677,3 @@ const parent: string = dirname(path), | ||
if (!listing[fileName]) { | ||
throw ErrnoError.With('ENOENT', path, 'removeEntry'); | ||
throw ErrnoError.With('ENOENT', path, 'remove'); | ||
} | ||
@@ -757,6 +686,2 @@ | ||
if (!fileNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'removeEntry'); | ||
} | ||
// Remove from directory listing of parent. | ||
@@ -766,7 +691,7 @@ delete listing[fileName]; | ||
if (!isDir && fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('EISDIR', path, 'removeEntry'); | ||
throw ErrnoError.With('EISDIR', path, 'remove'); | ||
} | ||
if (isDir && !fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'removeEntry'); | ||
throw ErrnoError.With('ENOTDIR', path, 'remove'); | ||
} | ||
@@ -792,3 +717,3 @@ | ||
*/ | ||
protected removeSync(path: string, isDir: boolean, cred: Cred): void { | ||
protected removeSync(path: string, isDir: boolean): void { | ||
using tx = this.store.transaction(); | ||
@@ -802,3 +727,3 @@ const parent: string = dirname(path), | ||
if (!fileIno) { | ||
throw ErrnoError.With('ENOENT', path, 'removeEntry'); | ||
throw ErrnoError.With('ENOENT', path, 'remove'); | ||
} | ||
@@ -809,6 +734,2 @@ | ||
if (!fileNode.toStats().hasAccess(W_OK, cred)) { | ||
throw ErrnoError.With('EACCES', path, 'removeEntry'); | ||
} | ||
// Remove from directory listing of parent. | ||
@@ -818,7 +739,7 @@ delete listing[fileName]; | ||
if (!isDir && fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('EISDIR', path, 'removeEntry'); | ||
throw ErrnoError.With('EISDIR', path, 'remove'); | ||
} | ||
if (isDir && !fileNode.toStats().isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', path, 'removeEntry'); | ||
throw ErrnoError.With('ENOTDIR', path, 'remove'); | ||
} | ||
@@ -825,0 +746,0 @@ |
import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js'; | ||
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js'; | ||
import { credentials } from './credentials.js'; | ||
import * as fs from './emulation/index.js'; | ||
import type { AbsolutePath } from './emulation/path.js'; | ||
import { setCred, type MountObject } from './emulation/shared.js'; | ||
import type { MountObject } from './emulation/shared.js'; | ||
import { Errno, ErrnoError } from './error.js'; | ||
@@ -110,3 +111,3 @@ import { FileSystem } from './filesystem.js'; | ||
setCred({ uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid }); | ||
Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid }); | ||
@@ -113,0 +114,0 @@ if (!config.mounts) { |
@@ -6,3 +6,3 @@ import { Buffer } from 'buffer'; | ||
import { BigIntStats, type Stats } from '../stats.js'; | ||
import { normalizeMode, type Callback } from '../utils.js'; | ||
import { normalizeMode, normalizePath, type Callback } from '../utils.js'; | ||
import { R_OK } from './constants.js'; | ||
@@ -14,3 +14,3 @@ import type { Dirent } from './dir.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
import { FSWatcher } from './watchers.js'; | ||
import { FSWatcher, StatWatcher } from './watchers.js'; | ||
@@ -678,4 +678,14 @@ const nop = () => {}; | ||
const statWatchers: Map<string, { watcher: StatWatcher; listeners: Set<(curr: Stats, prev: Stats) => void> }> = new Map(); | ||
/** | ||
* @todo Implement | ||
* Watch for changes on a file. The callback listener will be called each time the file is accessed. | ||
* | ||
* The `options` argument may be omitted. If provided, it should be an object with a `persistent` boolean and an `interval` number specifying the polling interval in milliseconds. | ||
* | ||
* When a change is detected, the `listener` callback is called with the current and previous `Stats` objects. | ||
* | ||
* @param path The path to the file to watch. | ||
* @param options Optional options object specifying `persistent` and `interval`. | ||
* @param listener The callback listener to be called when the file changes. | ||
*/ | ||
@@ -687,5 +697,34 @@ export function watchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void): void; | ||
optsListener: { persistent?: boolean; interval?: number } | ((curr: Stats, prev: Stats) => void), | ||
listener: (curr: Stats, prev: Stats) => void = nop | ||
listener?: (curr: Stats, prev: Stats) => void | ||
): void { | ||
throw ErrnoError.With('ENOSYS', path.toString(), 'watchFile'); | ||
const normalizedPath = normalizePath(path.toString()); | ||
const options: { persistent?: boolean; interval?: number } = typeof optsListener != 'function' ? optsListener : {}; | ||
if (typeof optsListener === 'function') { | ||
listener = optsListener; | ||
} | ||
if (!listener) { | ||
throw new ErrnoError(Errno.EINVAL, 'No listener specified', path.toString(), 'watchFile'); | ||
} | ||
if (statWatchers.has(normalizedPath)) { | ||
const entry = statWatchers.get(normalizedPath); | ||
if (entry) { | ||
entry.listeners.add(listener); | ||
} | ||
return; | ||
} | ||
const watcher = new StatWatcher(normalizedPath, options); | ||
watcher.on('change', (curr: Stats, prev: Stats) => { | ||
const entry = statWatchers.get(normalizedPath); | ||
if (!entry) { | ||
return; | ||
} | ||
for (const listener of entry.listeners) { | ||
listener(curr, prev); | ||
} | ||
}); | ||
statWatchers.set(normalizedPath, { watcher, listeners: new Set() }); | ||
} | ||
@@ -695,17 +734,34 @@ watchFile satisfies Omit<typeof fs.watchFile, '__promisify__'>; | ||
/** | ||
* @todo Implement | ||
* Stop watching for changes on a file. | ||
* | ||
* If the `listener` is specified, only that particular listener is removed. | ||
* If no `listener` is specified, all listeners are removed, and the file is no longer watched. | ||
* | ||
* @param path The path to the file to stop watching. | ||
* @param listener Optional listener to remove. | ||
*/ | ||
export function unwatchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void = nop): void { | ||
throw ErrnoError.With('ENOSYS', path.toString(), 'unwatchFile'); | ||
const normalizedPath = normalizePath(path.toString()); | ||
const entry = statWatchers.get(normalizedPath); | ||
if (entry) { | ||
if (listener && listener !== nop) { | ||
entry.listeners.delete(listener); | ||
} else { | ||
// If no listener is specified, remove all listeners | ||
entry.listeners.clear(); | ||
} | ||
if (entry.listeners.size === 0) { | ||
// No more listeners, stop the watcher | ||
entry.watcher.stop(); | ||
statWatchers.delete(normalizedPath); | ||
} | ||
} | ||
} | ||
unwatchFile satisfies Omit<typeof fs.unwatchFile, '__promisify__'>; | ||
export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): fs.FSWatcher; | ||
export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): fs.FSWatcher; | ||
export function watch( | ||
path: fs.PathLike, | ||
options?: fs.WatchOptions | ((event: string, filename: string) => any), | ||
listener?: (event: string, filename: string) => any | ||
): fs.FSWatcher { | ||
const watcher = new FSWatcher<string>(typeof options == 'object' ? options : {}); | ||
export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): FSWatcher; | ||
export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): FSWatcher; | ||
export function watch(path: fs.PathLike, options?: fs.WatchOptions | ((event: string, filename: string) => any), listener?: (event: string, filename: string) => any): FSWatcher { | ||
const watcher = new FSWatcher<string>(normalizePath(path), typeof options == 'object' ? options : {}); | ||
listener = typeof options == 'function' ? options : listener; | ||
@@ -778,3 +834,3 @@ watcher.on('change', listener || nop); | ||
.then(() => callback(error)) | ||
.catch(callback); | ||
.catch(nop); | ||
}, | ||
@@ -781,0 +837,0 @@ }); |
@@ -1,8 +0,8 @@ | ||
import type { Dirent as _Dirent, Dir as _Dir } from 'fs'; | ||
import type { Dir as _Dir, Dirent as _Dirent } from 'fs'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import type { Stats } from '../stats.js'; | ||
import type { Callback } from '../utils.js'; | ||
import type { Stats } from '../stats.js'; | ||
import { basename } from './path.js'; | ||
import { readdir } from './promises.js'; | ||
import { ErrnoError, Errno } from '../error.js'; | ||
import { readdirSync } from './sync.js'; | ||
import { basename } from './path.js'; | ||
@@ -58,11 +58,4 @@ export class Dirent implements _Dirent { | ||
protected _entries: Dirent[] = []; | ||
protected _entries?: Dirent[]; | ||
/** | ||
* @internal | ||
*/ | ||
public async _loadEntries() { | ||
this._entries ??= await readdir(this.path, { withFileTypes: true }); | ||
} | ||
public constructor(public readonly path: string) {} | ||
@@ -93,7 +86,8 @@ | ||
protected async _read(): Promise<Dirent | null> { | ||
await this._loadEntries(); | ||
this.checkClosed(); | ||
this._entries ??= await readdir(this.path, { withFileTypes: true }); | ||
if (!this._entries.length) { | ||
return null; | ||
} | ||
return this._entries.shift() || null; | ||
return this._entries.shift() ?? null; | ||
} | ||
@@ -122,2 +116,3 @@ | ||
public readSync(): Dirent | null { | ||
this.checkClosed(); | ||
this._entries ??= readdirSync(this.path, { withFileTypes: true }); | ||
@@ -127,5 +122,15 @@ if (!this._entries.length) { | ||
} | ||
return this._entries.shift() || null; | ||
return this._entries.shift() ?? null; | ||
} | ||
async next(): Promise<IteratorResult<Dirent>> { | ||
const value = await this._read(); | ||
if (value) { | ||
return { done: false, value }; | ||
} | ||
await this.close(); | ||
return { done: true, value: undefined }; | ||
} | ||
/** | ||
@@ -135,17 +140,4 @@ * Asynchronously iterates over the directory via `readdir(3)` until all entries have been read. | ||
public [Symbol.asyncIterator](): AsyncIterableIterator<Dirent> { | ||
const _this = this; | ||
return { | ||
[Symbol.asyncIterator]: this[Symbol.asyncIterator], | ||
async next(): Promise<IteratorResult<Dirent>> { | ||
const value = await _this._read(); | ||
if (value != null) { | ||
return { done: false, value }; | ||
} | ||
await _this.close(); | ||
return { done: true, value: undefined }; | ||
}, | ||
}; | ||
return this; | ||
} | ||
} |
@@ -12,3 +12,3 @@ /* eslint-disable @typescript-eslint/no-redundant-type-constituents */ | ||
import type { File } from '../file.js'; | ||
import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js'; | ||
import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js'; | ||
import type { FileContents } from '../filesystem.js'; | ||
@@ -21,5 +21,6 @@ import '../polyfills.js'; | ||
import { dirname, join, parse } from './path.js'; | ||
import { _statfs, cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { credentials } from '../credentials.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
import { FSWatcher } from './watchers.js'; | ||
import { FSWatcher, emitChange } from './watchers.js'; | ||
export * as constants from './constants.js'; | ||
@@ -48,4 +49,5 @@ | ||
*/ | ||
public chown(uid: number, gid: number): Promise<void> { | ||
return this.file.chown(uid, gid); | ||
public async chown(uid: number, gid: number): Promise<void> { | ||
await this.file.chown(uid, gid); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -57,3 +59,3 @@ | ||
*/ | ||
public chmod(mode: fs.Mode): Promise<void> { | ||
public async chmod(mode: fs.Mode): Promise<void> { | ||
const numMode = normalizeMode(mode, -1); | ||
@@ -63,3 +65,4 @@ if (numMode < 0) { | ||
} | ||
return this.file.chmod(numMode); | ||
await this.file.chmod(numMode); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -85,3 +88,3 @@ | ||
*/ | ||
public truncate(len?: number | null): Promise<void> { | ||
public async truncate(len?: number | null): Promise<void> { | ||
len ||= 0; | ||
@@ -91,3 +94,4 @@ if (len < 0) { | ||
} | ||
return this.file.truncate(len); | ||
await this.file.truncate(len); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -100,4 +104,5 @@ | ||
*/ | ||
public utimes(atime: string | number | Date, mtime: string | number | Date): Promise<void> { | ||
return this.file.utimes(normalizeTime(atime), normalizeTime(mtime)); | ||
public async utimes(atime: string | number | Date, mtime: string | number | Date): Promise<void> { | ||
await this.file.utimes(normalizeTime(atime), normalizeTime(mtime)); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -126,2 +131,3 @@ | ||
await this.file.write(encodedData, 0, encodedData.length); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -226,2 +232,5 @@ | ||
const stats = await this.file.stat(); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', this.file.path, 'stat'); | ||
} | ||
return opts?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -267,2 +276,3 @@ } | ||
const bytesWritten = await this.file.write(buffer, offset, length, position); | ||
emitChange('change', this.file.path); | ||
return { buffer, bytesWritten }; | ||
@@ -293,2 +303,3 @@ } | ||
await this.file.write(encodedData, 0, encodedData.length, 0); | ||
emitChange('change', this.file.path); | ||
} | ||
@@ -400,5 +411,9 @@ | ||
const dst = resolveMount(newPath); | ||
if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
} | ||
try { | ||
if (src.mountPoint == dst.mountPoint) { | ||
await src.fs.rename(src.path, dst.path, cred); | ||
await src.fs.rename(src.path, dst.path); | ||
emitChange('rename', oldPath.toString()); | ||
return; | ||
@@ -408,2 +423,3 @@ } | ||
await unlink(oldPath); | ||
emitChange('rename', oldPath.toString()); | ||
} catch (e) { | ||
@@ -422,3 +438,3 @@ throw fixError(e as Error, { [src.path]: oldPath, [dst.path]: newPath }); | ||
const { fs, path: resolved } = resolveMount(await realpath(path)); | ||
return await fs.exists(resolved, cred); | ||
return await fs.exists(resolved); | ||
} catch (e) { | ||
@@ -445,3 +461,6 @@ if (e instanceof ErrnoError && e.code == 'ENOENT') { | ||
try { | ||
const stats = await fs.stat(resolved, cred); | ||
const stats = await fs.stat(resolved); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
} | ||
return options?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -467,3 +486,3 @@ } catch (e) { | ||
try { | ||
const stats = await fs.stat(resolved, cred); | ||
const stats = await fs.stat(resolved); | ||
return options?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -497,3 +516,7 @@ } catch (e) { | ||
try { | ||
await fs.unlink(resolved, cred); | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
} | ||
await fs.unlink(resolved); | ||
emitChange('rename', path.toString()); | ||
} catch (e) { | ||
@@ -517,3 +540,5 @@ throw fixError(e as Error, { [resolved]: path }); | ||
if (!(await fs.exists(resolved, cred))) { | ||
const stats = await fs.stat(resolved).catch(() => null); | ||
if (!stats) { | ||
if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') { | ||
@@ -523,9 +548,16 @@ throw ErrnoError.With('ENOENT', path, '_open'); | ||
// Create the file | ||
const parentStats: Stats = await fs.stat(dirname(resolved), cred); | ||
if (parentStats && !parentStats.isDirectory()) { | ||
const parentStats: Stats = await fs.stat(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
} | ||
if (!parentStats.isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', dirname(path), '_open'); | ||
} | ||
return new FileHandle(await fs.createFile(resolved, flag, mode, cred)); | ||
return new FileHandle(await fs.createFile(resolved, flag, mode)); | ||
} | ||
if (!stats.hasAccess(flagToMode(flag), credentials)) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
} | ||
if (isExclusive(flag)) { | ||
@@ -535,5 +567,3 @@ throw ErrnoError.With('EEXIST', path, '_open'); | ||
if (!isTruncating(flag)) { | ||
return new FileHandle(await fs.openFile(resolved, flag, cred)); | ||
} | ||
const handle = new FileHandle(await fs.openFile(resolved, flag)); | ||
@@ -546,6 +576,8 @@ /* | ||
*/ | ||
const file: File = await fs.openFile(resolved, flag, cred); | ||
await file.truncate(0); | ||
await file.sync(); | ||
return new FileHandle(file); | ||
if (isTruncating(flag)) { | ||
await handle.truncate(0); | ||
await handle.sync(); | ||
} | ||
return handle; | ||
} | ||
@@ -656,3 +688,7 @@ | ||
try { | ||
await fs.rmdir(resolved, cred); | ||
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', resolved, 'rmdir'); | ||
} | ||
await fs.rmdir(resolved); | ||
emitChange('rename', path.toString()); | ||
} catch (e) { | ||
@@ -684,7 +720,12 @@ throw fixError(e as Error, { [resolved]: path }); | ||
if (!options?.recursive) { | ||
await fs.mkdir(resolved, mode, cred); | ||
if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
} | ||
await fs.mkdir(resolved, mode); | ||
emitChange('rename', path.toString()); | ||
return; | ||
} | ||
const dirs: string[] = []; | ||
for (let dir = resolved, origDir = path; !(await fs.exists(dir, cred)); dir = dirname(dir), origDir = dirname(origDir)) { | ||
for (let dir = resolved, origDir = path; !(await fs.exists(dir)); dir = dirname(dir), origDir = dirname(origDir)) { | ||
dirs.unshift(dir); | ||
@@ -694,3 +735,7 @@ errorPaths[dir] = origDir; | ||
for (const dir of dirs) { | ||
await fs.mkdir(dir, mode, cred); | ||
if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
} | ||
await fs.mkdir(dir, mode); | ||
emitChange('rename', dir); | ||
} | ||
@@ -721,2 +766,5 @@ return dirs[0]; | ||
path = normalizePath(path); | ||
if (!(await stat(path)).hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
} | ||
path = (await exists(path)) ? await realpath(path) : path; | ||
@@ -726,3 +774,3 @@ const { fs, path: resolved } = resolveMount(path); | ||
try { | ||
entries = await fs.readdir(resolved, cred); | ||
entries = await fs.readdir(resolved); | ||
} catch (e) { | ||
@@ -753,13 +801,27 @@ throw fixError(e as Error, { [resolved]: path }); | ||
* `link`. | ||
* @param existing | ||
* @param newpath | ||
* @param targetPath | ||
* @param linkPath | ||
*/ | ||
export async function link(existing: fs.PathLike, newpath: fs.PathLike): Promise<void> { | ||
existing = normalizePath(existing); | ||
newpath = normalizePath(newpath); | ||
const { fs, path: resolved } = resolveMount(newpath); | ||
export async function link(targetPath: fs.PathLike, linkPath: fs.PathLike): Promise<void> { | ||
targetPath = normalizePath(targetPath); | ||
if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
} | ||
const { fs, path } = resolveMount(targetPath); | ||
const link = resolveMount(linkPath); | ||
if (fs != link.fs) { | ||
throw ErrnoError.With('EXDEV', linkPath, 'link'); | ||
} | ||
try { | ||
return await fs.link(existing, newpath, cred); | ||
if (!(await fs.stat(path)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
} | ||
return await fs.link(path, link.path); | ||
} catch (e) { | ||
throw fixError(e as Error, { [resolved]: newpath }); | ||
throw fixError(e as Error, { [link.path]: linkPath, [path]: targetPath }); | ||
} | ||
@@ -891,3 +953,3 @@ } | ||
try { | ||
const stats = await fs.stat(resolvedPath, cred); | ||
const stats = await fs.stat(resolvedPath); | ||
if (!stats.isSymbolicLink()) { | ||
@@ -910,3 +972,3 @@ return lpath; | ||
[Symbol.asyncIterator](): AsyncIterator<FileChangeInfo<T>> { | ||
const watcher = new FSWatcher<T>(typeof options != 'string' ? options : { encoding: options as BufferEncoding | 'buffer' }); | ||
const watcher = new FSWatcher<T>(filename.toString(), typeof options != 'string' ? options : { encoding: options as BufferEncoding | 'buffer' }); | ||
@@ -940,3 +1002,3 @@ function withDone(done: boolean) { | ||
const stats = await stat(path); | ||
if (!stats.hasAccess(mode, cred)) { | ||
if (!stats.hasAccess(mode, credentials)) { | ||
throw new ErrnoError(Errno.EACCES); | ||
@@ -1015,2 +1077,3 @@ } | ||
await writeFile(dest, await readFile(src)); | ||
emitChange('rename', dest.toString()); | ||
} | ||
@@ -1028,5 +1091,3 @@ copyFile satisfies typeof promises.copyFile; | ||
path = normalizePath(path); | ||
const dir = new Dir(path); | ||
await dir._loadEntries(); | ||
return dir; | ||
return new Dir(path); | ||
} | ||
@@ -1033,0 +1094,0 @@ opendir satisfies typeof promises.opendir; |
@@ -5,4 +5,2 @@ // Utilities and shared data | ||
import { InMemory } from '../backends/memory.js'; | ||
import type { Cred } from '../cred.js'; | ||
import { rootCred } from '../cred.js'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
@@ -15,8 +13,2 @@ import type { File } from '../file.js'; | ||
// credentials | ||
export let cred: Cred = rootCred; | ||
export function setCred(val: Cred): void { | ||
cred = val; | ||
} | ||
// descriptors | ||
@@ -23,0 +15,0 @@ export const fdMap: Map<number, File> = new Map(); |
@@ -5,10 +5,12 @@ import { Buffer } from 'buffer'; | ||
import type { File } from '../file.js'; | ||
import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js'; | ||
import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js'; | ||
import type { FileContents } from '../filesystem.js'; | ||
import { BigIntStats, type Stats } from '../stats.js'; | ||
import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js'; | ||
import { COPYFILE_EXCL, F_OK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK } from './constants.js'; | ||
import * as constants from './constants.js'; | ||
import { Dir, Dirent } from './dir.js'; | ||
import { dirname, join, parse } from './path.js'; | ||
import { _statfs, cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js'; | ||
import { credentials } from '../credentials.js'; | ||
import { emitChange } from './watchers.js'; | ||
@@ -23,8 +25,12 @@ /** | ||
newPath = normalizePath(newPath); | ||
const _old = resolveMount(oldPath); | ||
const _new = resolveMount(newPath); | ||
const paths = { [_old.path]: oldPath, [_new.path]: newPath }; | ||
const oldMount = resolveMount(oldPath); | ||
const newMount = resolveMount(newPath); | ||
if (!statSync(dirname(oldPath)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', oldPath, 'rename'); | ||
} | ||
try { | ||
if (_old === _new) { | ||
return _old.fs.renameSync(_old.path, _new.path, cred); | ||
if (oldMount === newMount) { | ||
oldMount.fs.renameSync(oldMount.path, newMount.path); | ||
emitChange('rename', oldPath.toString()); | ||
return; | ||
} | ||
@@ -34,4 +40,5 @@ | ||
unlinkSync(oldPath); | ||
emitChange('rename', oldPath.toString()); | ||
} catch (e) { | ||
throw fixError(e as Error, paths); | ||
throw fixError(e as Error, { [oldMount.path]: oldPath, [newMount.path]: newPath }); | ||
} | ||
@@ -49,3 +56,3 @@ } | ||
const { fs, path: resolvedPath } = resolveMount(realpathSync(path)); | ||
return fs.existsSync(resolvedPath, cred); | ||
return fs.existsSync(resolvedPath); | ||
} catch (e) { | ||
@@ -72,3 +79,6 @@ if ((e as ErrnoError).errno == Errno.ENOENT) { | ||
try { | ||
const stats = fs.statSync(resolved, cred); | ||
const stats = fs.statSync(resolved); | ||
if (!stats.hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'stat'); | ||
} | ||
return options?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -93,3 +103,3 @@ } catch (e) { | ||
try { | ||
const stats = fs.statSync(resolved, cred); | ||
const stats = fs.statSync(resolved); | ||
return options?.bigint ? new BigIntStats(stats) : stats; | ||
@@ -125,3 +135,7 @@ } catch (e) { | ||
try { | ||
return fs.unlinkSync(resolved, cred); | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', resolved, 'unlink'); | ||
} | ||
fs.unlinkSync(resolved); | ||
emitChange('rename', path.toString()); | ||
} catch (e) { | ||
@@ -141,3 +155,3 @@ throw fixError(e as Error, { [resolved]: path }); | ||
if (!fs.existsSync(resolved, cred)) { | ||
if (!fs.existsSync(resolved)) { | ||
if ((!isWriteable(flag) && !isAppendable(flag)) || flag == 'r+') { | ||
@@ -147,12 +161,15 @@ throw ErrnoError.With('ENOENT', path, '_open'); | ||
// Create the file | ||
const parentStats: Stats = fs.statSync(dirname(resolved), cred); | ||
const parentStats: Stats = fs.statSync(dirname(resolved)); | ||
if (!parentStats.hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(path), '_open'); | ||
} | ||
if (!parentStats.isDirectory()) { | ||
throw ErrnoError.With('ENOTDIR', dirname(path), '_open'); | ||
} | ||
return fs.createFileSync(resolved, flag, mode, cred); | ||
return fs.createFileSync(resolved, flag, mode); | ||
} | ||
const stats: Stats = fs.statSync(resolved, cred); | ||
const stats: Stats = fs.statSync(resolved); | ||
if (!stats.hasAccess(mode, cred)) { | ||
if (!stats.hasAccess(mode, credentials) || !stats.hasAccess(flagToMode(flag), credentials)) { | ||
throw ErrnoError.With('EACCES', path, '_open'); | ||
@@ -165,15 +182,10 @@ } | ||
if (!isTruncating(flag)) { | ||
return fs.openFileSync(resolved, flag, cred); | ||
const file = fs.openFileSync(resolved, flag); | ||
if (isTruncating(flag)) { | ||
file.truncateSync(0); | ||
file.syncSync(); | ||
} | ||
// Delete file. | ||
fs.unlinkSync(resolved, cred); | ||
/* | ||
Create file. Use the same mode as the old file. | ||
Node itself modifies the ctime when this occurs, so this action | ||
will preserve that behavior if the underlying file system | ||
supports those properties. | ||
*/ | ||
return fs.createFileSync(resolved, flag, stats.mode, cred); | ||
return file; | ||
} | ||
@@ -189,3 +201,3 @@ | ||
*/ | ||
export function openSync(path: fs.PathLike, flag: fs.OpenMode, mode: fs.Mode | null = F_OK): number { | ||
export function openSync(path: fs.PathLike, flag: fs.OpenMode, mode: fs.Mode | null = constants.F_OK): number { | ||
return file2fd(_openSync(path, flag, mode, true)); | ||
@@ -266,2 +278,3 @@ } | ||
file.writeSync(encodedData, 0, encodedData.byteLength, 0); | ||
emitChange('change', path.toString()); | ||
} | ||
@@ -386,3 +399,5 @@ writeFileSync satisfies typeof fs.writeFileSync; | ||
position ??= file.position; | ||
return file.writeSync(buffer, offset, length, position); | ||
const bytesWritten = file.writeSync(buffer, offset, length, position); | ||
emitChange('change', file.path); | ||
return bytesWritten; | ||
} | ||
@@ -467,3 +482,7 @@ writeSync satisfies typeof fs.writeSync; | ||
try { | ||
fs.rmdirSync(resolved, cred); | ||
if (!fs.statSync(resolved).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', resolved, 'rmdir'); | ||
} | ||
fs.rmdirSync(resolved); | ||
emitChange('rename', path.toString()); | ||
} catch (e) { | ||
@@ -479,3 +498,2 @@ throw fixError(e as Error, { [resolved]: path }); | ||
* @param mode defaults to o777 | ||
* @todo Implement recursion | ||
*/ | ||
@@ -496,7 +514,10 @@ export function mkdirSync(path: fs.PathLike, options: fs.MakeDirectoryOptions & { recursive: true }): string | undefined; | ||
if (!options?.recursive) { | ||
return fs.mkdirSync(resolved, mode, cred); | ||
if (!fs.statSync(dirname(resolved)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir'); | ||
} | ||
return fs.mkdirSync(resolved, mode); | ||
} | ||
const dirs: string[] = []; | ||
for (let dir = resolved, original = path; !fs.existsSync(dir, cred); dir = dirname(dir), original = dirname(original)) { | ||
for (let dir = resolved, original = path; !fs.existsSync(dir); dir = dirname(dir), original = dirname(original)) { | ||
dirs.unshift(dir); | ||
@@ -506,3 +527,7 @@ errorPaths[dir] = original; | ||
for (const dir of dirs) { | ||
fs.mkdirSync(dir, mode, cred); | ||
if (!fs.statSync(dirname(dir)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir'); | ||
} | ||
fs.mkdirSync(dir, mode); | ||
emitChange('rename', dir); | ||
} | ||
@@ -531,4 +556,7 @@ return dirs[0]; | ||
let entries: string[]; | ||
if (!statSync(path).hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'readdir'); | ||
} | ||
try { | ||
entries = fs.readdirSync(resolved, cred); | ||
entries = fs.readdirSync(resolved); | ||
} catch (e) { | ||
@@ -566,13 +594,27 @@ throw fixError(e as Error, { [resolved]: path }); | ||
* Synchronous `link`. | ||
* @param existing | ||
* @param newpath | ||
* @param targetPath | ||
* @param linkPath | ||
*/ | ||
export function linkSync(existing: fs.PathLike, newpath: fs.PathLike): void { | ||
existing = normalizePath(existing); | ||
newpath = normalizePath(newpath); | ||
const { fs, path: resolved } = resolveMount(existing); | ||
export function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void { | ||
targetPath = normalizePath(targetPath); | ||
if (!statSync(dirname(targetPath)).hasAccess(constants.R_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(targetPath), 'link'); | ||
} | ||
linkPath = normalizePath(linkPath); | ||
if (!statSync(dirname(linkPath)).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', dirname(linkPath), 'link'); | ||
} | ||
const { fs, path } = resolveMount(targetPath); | ||
const link = resolveMount(linkPath); | ||
if (fs != link.fs) { | ||
throw ErrnoError.With('EXDEV', linkPath, 'link'); | ||
} | ||
try { | ||
return fs.linkSync(resolved, newpath, cred); | ||
if (!fs.statSync(path).hasAccess(constants.W_OK, credentials)) { | ||
throw ErrnoError.With('EACCES', path, 'link'); | ||
} | ||
return fs.linkSync(path, linkPath); | ||
} catch (e) { | ||
throw fixError(e as Error, { [resolved]: existing }); | ||
throw fixError(e as Error, { [path]: targetPath, [link.path]: linkPath }); | ||
} | ||
@@ -598,3 +640,3 @@ } | ||
const file = _openSync(path, 'r+', 0o644, false); | ||
file._setTypeSync(S_IFLNK); | ||
file._setTypeSync(constants.S_IFLNK); | ||
} | ||
@@ -715,3 +757,3 @@ symlinkSync satisfies typeof fs.symlinkSync; | ||
try { | ||
const stats = fs.statSync(resolvedPath, cred); | ||
const stats = fs.statSync(resolvedPath); | ||
if (!stats.isSymbolicLink()) { | ||
@@ -735,3 +777,3 @@ return lpath; | ||
const stats = statSync(path); | ||
if (!stats.hasAccess(mode, cred)) { | ||
if (!stats.hasAccess(mode, credentials)) { | ||
throw new ErrnoError(Errno.EACCES); | ||
@@ -751,4 +793,4 @@ } | ||
switch (stats.mode & S_IFMT) { | ||
case S_IFDIR: | ||
switch (stats.mode & constants.S_IFMT) { | ||
case constants.S_IFDIR: | ||
if (options?.recursive) { | ||
@@ -762,10 +804,10 @@ for (const entry of readdirSync(path)) { | ||
return; | ||
case S_IFREG: | ||
case S_IFLNK: | ||
case constants.S_IFREG: | ||
case constants.S_IFLNK: | ||
unlinkSync(path); | ||
return; | ||
case S_IFBLK: | ||
case S_IFCHR: | ||
case S_IFIFO: | ||
case S_IFSOCK: | ||
case constants.S_IFBLK: | ||
case constants.S_IFCHR: | ||
case constants.S_IFIFO: | ||
case constants.S_IFSOCK: | ||
default: | ||
@@ -807,3 +849,3 @@ throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm'); | ||
if (flags && flags & COPYFILE_EXCL && existsSync(dest)) { | ||
if (flags && flags & constants.COPYFILE_EXCL && existsSync(dest)) { | ||
throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', dest, 'copyFile'); | ||
@@ -813,2 +855,3 @@ } | ||
writeFileSync(dest, readFileSync(src)); | ||
emitChange('rename', dest.toString()); | ||
} | ||
@@ -863,3 +906,3 @@ copyFileSync satisfies typeof fs.copyFileSync; | ||
path = normalizePath(path); | ||
return new Dir(path); // Re-use existing `Dir` class | ||
return new Dir(path); | ||
} | ||
@@ -890,4 +933,4 @@ opendirSync satisfies typeof fs.opendirSync; | ||
switch (srcStats.mode & S_IFMT) { | ||
case S_IFDIR: | ||
switch (srcStats.mode & constants.S_IFMT) { | ||
case constants.S_IFDIR: | ||
if (!opts?.recursive) { | ||
@@ -904,10 +947,10 @@ throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp'); | ||
break; | ||
case S_IFREG: | ||
case S_IFLNK: | ||
case constants.S_IFREG: | ||
case constants.S_IFLNK: | ||
copyFileSync(source, destination); | ||
break; | ||
case S_IFBLK: | ||
case S_IFCHR: | ||
case S_IFIFO: | ||
case S_IFSOCK: | ||
case constants.S_IFBLK: | ||
case constants.S_IFCHR: | ||
case constants.S_IFIFO: | ||
case constants.S_IFSOCK: | ||
default: | ||
@@ -914,0 +957,0 @@ throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm'); |
@@ -5,3 +5,13 @@ import { EventEmitter } from 'eventemitter3'; | ||
import { ErrnoError } from '../error.js'; | ||
import { isStatsEqual, type Stats } from '../stats.js'; | ||
import { normalizePath } from '../utils.js'; | ||
import { dirname, basename } from './path.js'; | ||
import { statSync } from './sync.js'; | ||
/** | ||
* Base class for file system watchers. | ||
* Provides event handling capabilities for watching file system changes. | ||
* | ||
* @template TEvents The type of events emitted by the watcher. | ||
*/ | ||
class Watcher<TEvents extends Record<string, unknown[]> = Record<string, unknown[]>> extends EventEmitter<TEvents> implements NodeEventEmitter { | ||
@@ -18,20 +28,24 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
public constructor(public readonly path: string) { | ||
super(); | ||
} | ||
public setMaxListeners(): never { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.setMaxListeners'); | ||
} | ||
public getMaxListeners(): never { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.getMaxListeners'); | ||
} | ||
public prependListener(): never { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.prependListener'); | ||
} | ||
public prependOnceListener(): never { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.prependOnceListener'); | ||
} | ||
public rawListeners(): never { | ||
throw ErrnoError.With('ENOTSUP'); | ||
throw ErrnoError.With('ENOSYS', this.path, 'Watcher.rawListeners'); | ||
} | ||
@@ -49,3 +63,5 @@ | ||
/** | ||
* @todo Actually emit events | ||
* Watches for changes on the file system. | ||
* | ||
* @template T The type of the filename, either `string` or `Buffer`. | ||
*/ | ||
@@ -60,8 +76,122 @@ export class FSWatcher<T extends string | Buffer = string | Buffer> | ||
{ | ||
public constructor(public readonly options: fs.WatchOptions) { | ||
super(); | ||
public constructor( | ||
path: string, | ||
public readonly options: fs.WatchOptions | ||
) { | ||
super(path); | ||
addWatcher(path.toString(), this); | ||
} | ||
public close(): void {} | ||
public close(): void { | ||
super.emit('close'); | ||
removeWatcher(this.path.toString(), this); | ||
} | ||
public [Symbol.dispose](): void { | ||
this.close(); | ||
} | ||
} | ||
export class StatWatcher extends Watcher implements fs.StatWatcher {} | ||
/** | ||
* Watches for changes to a file's stats. | ||
* | ||
* Instances of `StatWatcher` are used by `fs.watchFile()` to monitor changes to a file's statistics. | ||
*/ | ||
export class StatWatcher | ||
extends Watcher<{ | ||
change: [current: Stats, previous: Stats]; | ||
close: []; | ||
error: [error: Error]; | ||
}> | ||
implements fs.StatWatcher | ||
{ | ||
private intervalId?: NodeJS.Timeout | number; | ||
private previous?: Stats; | ||
public constructor( | ||
path: string, | ||
private options: { persistent?: boolean; interval?: number } | ||
) { | ||
super(path); | ||
this.start(); | ||
} | ||
protected onInterval() { | ||
try { | ||
const current = statSync(this.path); | ||
if (!isStatsEqual(this.previous!, current)) { | ||
this.emit('change', current, this.previous!); | ||
this.previous = current; | ||
} | ||
} catch (e) { | ||
this.emit('error', e as Error); | ||
} | ||
} | ||
protected start() { | ||
const interval = this.options.interval || 5000; | ||
try { | ||
this.previous = statSync(this.path); | ||
} catch (e) { | ||
this.emit('error', e as Error); | ||
return; | ||
} | ||
this.intervalId = setInterval(this.onInterval.bind(this), interval); | ||
if (!this.options.persistent && typeof this.intervalId == 'object') { | ||
this.intervalId.unref(); | ||
} | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
public stop() { | ||
if (this.intervalId) { | ||
clearInterval(this.intervalId); | ||
this.intervalId = undefined; | ||
} | ||
this.removeAllListeners(); | ||
} | ||
} | ||
const watchers: Map<string, Set<FSWatcher>> = new Map(); | ||
export function addWatcher(path: string, watcher: FSWatcher) { | ||
const normalizedPath = normalizePath(path); | ||
if (!watchers.has(normalizedPath)) { | ||
watchers.set(normalizedPath, new Set()); | ||
} | ||
watchers.get(normalizedPath)!.add(watcher); | ||
} | ||
export function removeWatcher(path: string, watcher: FSWatcher) { | ||
const normalizedPath = normalizePath(path); | ||
if (watchers.has(normalizedPath)) { | ||
watchers.get(normalizedPath)!.delete(watcher); | ||
if (watchers.get(normalizedPath)!.size === 0) { | ||
watchers.delete(normalizedPath); | ||
} | ||
} | ||
} | ||
export function emitChange(eventType: fs.WatchEventType, filename: string) { | ||
let normalizedFilename: string = normalizePath(filename); | ||
// Notify watchers on the specific file | ||
if (watchers.has(normalizedFilename)) { | ||
for (const watcher of watchers.get(normalizedFilename)!) { | ||
watcher.emit('change', eventType, basename(filename)); | ||
} | ||
} | ||
// Notify watchers on parent directories if they are watching recursively | ||
let parent = dirname(normalizedFilename); | ||
while (parent !== normalizedFilename && parent !== '/') { | ||
if (watchers.has(parent)) { | ||
for (const watcher of watchers.get(parent)!) { | ||
watcher.emit('change', eventType, basename(filename)); | ||
} | ||
} | ||
normalizedFilename = parent; | ||
parent = dirname(parent); | ||
} | ||
} |
@@ -287,3 +287,3 @@ /** | ||
/** | ||
* @return A friendly error message. | ||
* @returns A friendly error message. | ||
*/ | ||
@@ -290,0 +290,0 @@ public toString(): string { |
@@ -1,2 +0,1 @@ | ||
import type { Cred } from './cred.js'; | ||
import type { ErrnoError } from './error.js'; | ||
@@ -106,7 +105,7 @@ import type { File } from './file.js'; | ||
*/ | ||
public abstract rename(oldPath: string, newPath: string, cred: Cred): Promise<void>; | ||
public abstract rename(oldPath: string, newPath: string): Promise<void>; | ||
/** | ||
* Synchronous rename. | ||
*/ | ||
public abstract renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
public abstract renameSync(oldPath: string, newPath: string): void; | ||
@@ -116,3 +115,3 @@ /** | ||
*/ | ||
public abstract stat(path: string, cred: Cred): Promise<Stats>; | ||
public abstract stat(path: string): Promise<Stats>; | ||
@@ -122,3 +121,3 @@ /** | ||
*/ | ||
public abstract statSync(path: string, cred: Cred): Stats; | ||
public abstract statSync(path: string): Stats; | ||
@@ -130,3 +129,3 @@ /** | ||
*/ | ||
public abstract openFile(path: string, flag: string, cred: Cred): Promise<File>; | ||
public abstract openFile(path: string, flag: string): Promise<File>; | ||
@@ -139,3 +138,3 @@ /** | ||
*/ | ||
public abstract openFileSync(path: string, flag: string, cred: Cred): File; | ||
public abstract openFileSync(path: string, flag: string): File; | ||
@@ -145,3 +144,3 @@ /** | ||
*/ | ||
public abstract createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>; | ||
public abstract createFile(path: string, flag: string, mode: number): Promise<File>; | ||
@@ -151,3 +150,3 @@ /** | ||
*/ | ||
public abstract createFileSync(path: string, flag: string, mode: number, cred: Cred): File; | ||
public abstract createFileSync(path: string, flag: string, mode: number): File; | ||
@@ -157,7 +156,7 @@ /** | ||
*/ | ||
public abstract unlink(path: string, cred: Cred): Promise<void>; | ||
public abstract unlink(path: string): Promise<void>; | ||
/** | ||
* Synchronous `unlink`. | ||
*/ | ||
public abstract unlinkSync(path: string, cred: Cred): void; | ||
public abstract unlinkSync(path: string): void; | ||
// Directory operations | ||
@@ -167,7 +166,7 @@ /** | ||
*/ | ||
public abstract rmdir(path: string, cred: Cred): Promise<void>; | ||
public abstract rmdir(path: string): Promise<void>; | ||
/** | ||
* Synchronous `rmdir`. | ||
*/ | ||
public abstract rmdirSync(path: string, cred: Cred): void; | ||
public abstract rmdirSync(path: string): void; | ||
/** | ||
@@ -177,3 +176,3 @@ * Asynchronous `mkdir`. | ||
*/ | ||
public abstract mkdir(path: string, mode: number, cred: Cred): Promise<void>; | ||
public abstract mkdir(path: string, mode: number): Promise<void>; | ||
/** | ||
@@ -183,11 +182,11 @@ * Synchronous `mkdir`. | ||
*/ | ||
public abstract mkdirSync(path: string, mode: number, cred: Cred): void; | ||
public abstract mkdirSync(path: string, mode: number): void; | ||
/** | ||
* Asynchronous `readdir`. Reads the contents of a directory. | ||
*/ | ||
public abstract readdir(path: string, cred: Cred): Promise<string[]>; | ||
public abstract readdir(path: string): Promise<string[]>; | ||
/** | ||
* Synchronous `readdir`. Reads the contents of a directory. | ||
*/ | ||
public abstract readdirSync(path: string, cred: Cred): string[]; | ||
public abstract readdirSync(path: string): string[]; | ||
@@ -197,5 +196,5 @@ /** | ||
*/ | ||
public async exists(path: string, cred: Cred): Promise<boolean> { | ||
public async exists(path: string): Promise<boolean> { | ||
try { | ||
await this.stat(path, cred); | ||
await this.stat(path); | ||
return true; | ||
@@ -210,5 +209,5 @@ } catch (e) { | ||
*/ | ||
public existsSync(path: string, cred: Cred): boolean { | ||
public existsSync(path: string): boolean { | ||
try { | ||
this.statSync(path, cred); | ||
this.statSync(path); | ||
return true; | ||
@@ -223,3 +222,3 @@ } catch (e) { | ||
*/ | ||
public abstract link(srcpath: string, dstpath: string, cred: Cred): Promise<void>; | ||
public abstract link(target: string, link: string): Promise<void>; | ||
@@ -229,3 +228,3 @@ /** | ||
*/ | ||
public abstract linkSync(srcpath: string, dstpath: string, cred: Cred): void; | ||
public abstract linkSync(target: string, link: string): void; | ||
@@ -232,0 +231,0 @@ /** |
@@ -12,3 +12,3 @@ export * from './error.js'; | ||
export * from './config.js'; | ||
export * from './cred.js'; | ||
export * from './credentials.js'; | ||
export * from './file.js'; | ||
@@ -15,0 +15,0 @@ export * from './filesystem.js'; |
@@ -1,5 +0,4 @@ | ||
import { rootCred, type Cred } from '../cred.js'; | ||
import { join } from '../emulation/path.js'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
import { PreloadFile, parseFlag, type File } from '../file.js'; | ||
import { parseFlag, PreloadFile, type File } from '../file.js'; | ||
import type { FileSystem } from '../filesystem.js'; | ||
@@ -20,7 +19,7 @@ import type { Stats } from '../stats.js'; | ||
* Implementing classes must define `_sync` for the synchronous file system used as a cache. | ||
* Synchronous methods on an asynchronous FS are implemented by: | ||
* - Performing operations over the in-memory copy, | ||
* while asynchronously pipelining them to the backing store. | ||
* - During loading, the contents of the async file system are preloaded into the synchronous store. | ||
* | ||
* Synchronous methods on an asynchronous FS are implemented by performing operations over the in-memory copy, | ||
* while asynchronously pipelining them to the backing store. | ||
* During loading, the contents of the async file system are preloaded into the synchronous store. | ||
* | ||
*/ | ||
@@ -38,11 +37,11 @@ export function Async<T extends typeof FileSystem>( | ||
ready(): Promise<void>; | ||
renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
statSync(path: string, cred: Cred): Stats; | ||
createFileSync(path: string, flag: string, mode: number, cred: Cred): File; | ||
openFileSync(path: string, flag: string, cred: Cred): File; | ||
unlinkSync(path: string, cred: Cred): void; | ||
rmdirSync(path: string, cred: Cred): void; | ||
mkdirSync(path: string, mode: number, cred: Cred): void; | ||
readdirSync(path: string, cred: Cred): string[]; | ||
linkSync(srcpath: string, dstpath: string, cred: Cred): void; | ||
renameSync(oldPath: string, newPath: string): void; | ||
statSync(path: string): Stats; | ||
createFileSync(path: string, flag: string, mode: number): File; | ||
openFileSync(path: string, flag: string): File; | ||
unlinkSync(path: string): void; | ||
rmdirSync(path: string): void; | ||
mkdirSync(path: string, mode: number): void; | ||
readdirSync(path: string): string[]; | ||
linkSync(srcpath: string, dstpath: string): void; | ||
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void; | ||
@@ -98,23 +97,23 @@ } | ||
public renameSync(oldPath: string, newPath: string, cred: Cred): void { | ||
public renameSync(oldPath: string, newPath: string): void { | ||
this.checkSync(oldPath, 'rename'); | ||
this._sync.renameSync(oldPath, newPath, cred); | ||
this.queue('rename', oldPath, newPath, cred); | ||
this._sync.renameSync(oldPath, newPath); | ||
this.queue('rename', oldPath, newPath); | ||
} | ||
public statSync(path: string, cred: Cred): Stats { | ||
public statSync(path: string): Stats { | ||
this.checkSync(path, 'stat'); | ||
return this._sync.statSync(path, cred); | ||
return this._sync.statSync(path); | ||
} | ||
public createFileSync(path: string, flag: string, mode: number, cred: Cred): PreloadFile<this> { | ||
public createFileSync(path: string, flag: string, mode: number): PreloadFile<this> { | ||
this.checkSync(path, 'createFile'); | ||
this._sync.createFileSync(path, flag, mode, cred); | ||
this.queue('createFile', path, flag, mode, cred); | ||
return this.openFileSync(path, flag, cred); | ||
this._sync.createFileSync(path, flag, mode); | ||
this.queue('createFile', path, flag, mode); | ||
return this.openFileSync(path, flag); | ||
} | ||
public openFileSync(path: string, flag: string, cred: Cred): PreloadFile<this> { | ||
public openFileSync(path: string, flag: string): PreloadFile<this> { | ||
this.checkSync(path, 'openFile'); | ||
const file = this._sync.openFileSync(path, flag, cred); | ||
const file = this._sync.openFileSync(path, flag); | ||
const stats = file.statSync(); | ||
@@ -126,29 +125,29 @@ const buffer = new Uint8Array(stats.size); | ||
public unlinkSync(path: string, cred: Cred): void { | ||
public unlinkSync(path: string): void { | ||
this.checkSync(path, 'unlinkSync'); | ||
this._sync.unlinkSync(path, cred); | ||
this.queue('unlink', path, cred); | ||
this._sync.unlinkSync(path); | ||
this.queue('unlink', path); | ||
} | ||
public rmdirSync(path: string, cred: Cred): void { | ||
public rmdirSync(path: string): void { | ||
this.checkSync(path, 'rmdir'); | ||
this._sync.rmdirSync(path, cred); | ||
this.queue('rmdir', path, cred); | ||
this._sync.rmdirSync(path); | ||
this.queue('rmdir', path); | ||
} | ||
public mkdirSync(path: string, mode: number, cred: Cred): void { | ||
public mkdirSync(path: string, mode: number): void { | ||
this.checkSync(path, 'mkdir'); | ||
this._sync.mkdirSync(path, mode, cred); | ||
this.queue('mkdir', path, mode, cred); | ||
this._sync.mkdirSync(path, mode); | ||
this.queue('mkdir', path, mode); | ||
} | ||
public readdirSync(path: string, cred: Cred): string[] { | ||
public readdirSync(path: string): string[] { | ||
this.checkSync(path, 'readdir'); | ||
return this._sync.readdirSync(path, cred); | ||
return this._sync.readdirSync(path); | ||
} | ||
public linkSync(srcpath: string, dstpath: string, cred: Cred): void { | ||
public linkSync(srcpath: string, dstpath: string): void { | ||
this.checkSync(srcpath, 'link'); | ||
this._sync.linkSync(srcpath, dstpath, cred); | ||
this.queue('link', srcpath, dstpath, cred); | ||
this._sync.linkSync(srcpath, dstpath); | ||
this.queue('link', srcpath, dstpath); | ||
} | ||
@@ -162,5 +161,5 @@ | ||
public existsSync(path: string, cred: Cred): boolean { | ||
public existsSync(path: string): boolean { | ||
this.checkSync(path, 'exists'); | ||
return this._sync.existsSync(path, cred); | ||
return this._sync.existsSync(path); | ||
} | ||
@@ -173,19 +172,19 @@ | ||
this.checkSync(path, 'crossCopy'); | ||
const stats = await this.stat(path, rootCred); | ||
if (stats.isDirectory()) { | ||
if (path !== '/') { | ||
const stats = await this.stat(path, rootCred); | ||
this._sync.mkdirSync(path, stats.mode, stats.cred()); | ||
} | ||
const files = await this.readdir(path, rootCred); | ||
for (const file of files) { | ||
await this.crossCopy(join(path, file)); | ||
} | ||
} else { | ||
await using asyncFile = await this.openFile(path, parseFlag('r'), rootCred); | ||
using syncFile = this._sync.createFileSync(path, parseFlag('w'), stats.mode, stats.cred()); | ||
const stats = await this.stat(path); | ||
if (!stats.isDirectory()) { | ||
await using asyncFile = await this.openFile(path, parseFlag('r')); | ||
using syncFile = this._sync.createFileSync(path, parseFlag('w'), stats.mode); | ||
const buffer = new Uint8Array(stats.size); | ||
await asyncFile.read(buffer); | ||
syncFile.writeSync(buffer, 0, stats.size); | ||
return; | ||
} | ||
if (path !== '/') { | ||
const stats = await this.stat(path); | ||
this._sync.mkdirSync(path, stats.mode); | ||
} | ||
const files = await this.readdir(path); | ||
for (const file of files) { | ||
await this.crossCopy(join(path, file)); | ||
} | ||
} | ||
@@ -192,0 +191,0 @@ |
@@ -1,2 +0,1 @@ | ||
import type { Cred } from '../cred.js'; | ||
import { ErrnoError } from '../error.js'; | ||
@@ -57,3 +56,3 @@ import type { File } from '../file.js'; | ||
{ | ||
lock(path: string): Promise<MutexLock>; | ||
lock(path: string, syscall: string): Promise<MutexLock>; | ||
lockSync(path: string): MutexLock; | ||
@@ -84,5 +83,13 @@ isLocked(path: string): boolean; | ||
*/ | ||
public async lock(path: string): Promise<MutexLock> { | ||
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(); | ||
@@ -100,3 +107,3 @@ return lock; | ||
// Non-null assertion: we already checked locks has path | ||
throw ErrnoError.With('EBUSY', path, 'lockSync'); | ||
throw ErrnoError.With('EBUSY', path, 'lock'); | ||
} | ||
@@ -116,122 +123,122 @@ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> { | ||
using _ = await this.lock(oldPath); | ||
public async rename(oldPath: string, newPath: string): Promise<void> { | ||
using _ = await this.lock(oldPath, 'rename'); | ||
// @ts-expect-error 2513 | ||
await super.rename(oldPath, newPath, cred); | ||
await super.rename(oldPath, newPath); | ||
} | ||
public renameSync(oldPath: string, newPath: string, cred: Cred): void { | ||
public renameSync(oldPath: string, newPath: string): void { | ||
using _ = this.lockSync(oldPath); | ||
// @ts-expect-error 2513 | ||
return super.renameSync(oldPath, newPath, cred); | ||
return super.renameSync(oldPath, newPath); | ||
} | ||
public async stat(path: string, cred: Cred): Promise<Stats> { | ||
using _ = await this.lock(path); | ||
public async stat(path: string): Promise<Stats> { | ||
using _ = await this.lock(path, 'stat'); | ||
// @ts-expect-error 2513 | ||
return await super.stat(path, cred); | ||
return await super.stat(path); | ||
} | ||
public statSync(path: string, cred: Cred): Stats { | ||
public statSync(path: string): Stats { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.statSync(path, cred); | ||
return super.statSync(path); | ||
} | ||
public async openFile(path: string, flag: string, cred: Cred): Promise<File> { | ||
using _ = await this.lock(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, cred); | ||
return await super.openFile(path, flag); | ||
} | ||
public openFileSync(path: string, flag: string, cred: Cred): File { | ||
public openFileSync(path: string, flag: string): File { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.openFileSync(path, flag, cred); | ||
return super.openFileSync(path, flag); | ||
} | ||
public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> { | ||
using _ = await this.lock(path); | ||
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, cred); | ||
return await super.createFile(path, flag, mode); | ||
} | ||
public createFileSync(path: string, flag: string, mode: number, cred: Cred): File { | ||
public createFileSync(path: string, flag: string, mode: number): File { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.createFileSync(path, flag, mode, cred); | ||
return super.createFileSync(path, flag, mode); | ||
} | ||
public async unlink(path: string, cred: Cred): Promise<void> { | ||
using _ = await this.lock(path); | ||
public async unlink(path: string): Promise<void> { | ||
using _ = await this.lock(path, 'unlink'); | ||
// @ts-expect-error 2513 | ||
await super.unlink(path, cred); | ||
await super.unlink(path); | ||
} | ||
public unlinkSync(path: string, cred: Cred): void { | ||
public unlinkSync(path: string): void { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.unlinkSync(path, cred); | ||
return super.unlinkSync(path); | ||
} | ||
public async rmdir(path: string, cred: Cred): Promise<void> { | ||
using _ = await this.lock(path); | ||
public async rmdir(path: string): Promise<void> { | ||
using _ = await this.lock(path, 'rmdir'); | ||
// @ts-expect-error 2513 | ||
await super.rmdir(path, cred); | ||
await super.rmdir(path); | ||
} | ||
public rmdirSync(path: string, cred: Cred): void { | ||
public rmdirSync(path: string): void { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.rmdirSync(path, cred); | ||
return super.rmdirSync(path); | ||
} | ||
public async mkdir(path: string, mode: number, cred: Cred): Promise<void> { | ||
using _ = await this.lock(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, cred); | ||
await super.mkdir(path, mode); | ||
} | ||
public mkdirSync(path: string, mode: number, cred: Cred): void { | ||
public mkdirSync(path: string, mode: number): void { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.mkdirSync(path, mode, cred); | ||
return super.mkdirSync(path, mode); | ||
} | ||
public async readdir(path: string, cred: Cred): Promise<string[]> { | ||
using _ = await this.lock(path); | ||
public async readdir(path: string): Promise<string[]> { | ||
using _ = await this.lock(path, 'readdir'); | ||
// @ts-expect-error 2513 | ||
return await super.readdir(path, cred); | ||
return await super.readdir(path); | ||
} | ||
public readdirSync(path: string, cred: Cred): string[] { | ||
public readdirSync(path: string): string[] { | ||
using _ = this.lockSync(path); | ||
// @ts-expect-error 2513 | ||
return super.readdirSync(path, cred); | ||
return super.readdirSync(path); | ||
} | ||
public async exists(path: string, cred: Cred): Promise<boolean> { | ||
using _ = await this.lock(path); | ||
return await super.exists(path, cred); | ||
public async exists(path: string): Promise<boolean> { | ||
using _ = await this.lock(path, 'exists'); | ||
return await super.exists(path); | ||
} | ||
public existsSync(path: string, cred: Cred): boolean { | ||
public existsSync(path: string): boolean { | ||
using _ = this.lockSync(path); | ||
return super.existsSync(path, cred); | ||
return super.existsSync(path); | ||
} | ||
public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> { | ||
using _ = await this.lock(srcpath); | ||
public async link(srcpath: string, dstpath: string): Promise<void> { | ||
using _ = await this.lock(srcpath, 'link'); | ||
// @ts-expect-error 2513 | ||
await super.link(srcpath, dstpath, cred); | ||
await super.link(srcpath, dstpath); | ||
} | ||
public linkSync(srcpath: string, dstpath: string, cred: Cred): void { | ||
public linkSync(srcpath: string, dstpath: string): void { | ||
using _ = this.lockSync(srcpath); | ||
// @ts-expect-error 2513 | ||
return super.linkSync(srcpath, dstpath, cred); | ||
return super.linkSync(srcpath, dstpath); | ||
} | ||
public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> { | ||
using _ = await this.lock(path); | ||
using _ = await this.lock(path, 'sync'); | ||
// @ts-expect-error 2513 | ||
@@ -238,0 +245,0 @@ await super.sync(path, data, stats); |
@@ -1,2 +0,1 @@ | ||
import type { Cred } from '../cred.js'; | ||
import { Errno, ErrnoError } from '../error.js'; | ||
@@ -18,14 +17,14 @@ import type { File } from '../file.js'; | ||
metadata(): FileSystemMetadata; | ||
rename(oldPath: string, newPath: string, cred: Cred): Promise<void>; | ||
renameSync(oldPath: string, newPath: string, cred: Cred): void; | ||
createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>; | ||
createFileSync(path: string, flag: string, mode: number, cred: Cred): File; | ||
unlink(path: string, cred: Cred): Promise<void>; | ||
unlinkSync(path: string, cred: Cred): void; | ||
rmdir(path: string, cred: Cred): Promise<void>; | ||
rmdirSync(path: string, cred: Cred): void; | ||
mkdir(path: string, mode: number, cred: Cred): Promise<void>; | ||
mkdirSync(path: string, mode: number, cred: Cred): void; | ||
link(srcpath: string, dstpath: string, cred: Cred): Promise<void>; | ||
linkSync(srcpath: string, dstpath: string, cred: Cred): void; | ||
rename(oldPath: string, newPath: string): Promise<void>; | ||
renameSync(oldPath: string, newPath: string): void; | ||
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; | ||
link(srcpath: string, dstpath: string): Promise<void>; | ||
linkSync(srcpath: string, dstpath: string): void; | ||
sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>; | ||
@@ -40,47 +39,47 @@ syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void; | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> { | ||
public async rename(oldPath: string, newPath: string): Promise<void> { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public renameSync(oldPath: string, newPath: string, cred: Cred): void { | ||
public renameSync(oldPath: string, newPath: string): void { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> { | ||
public async createFile(path: string, flag: string, mode: number): Promise<File> { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public createFileSync(path: string, flag: string, mode: number, cred: Cred): File { | ||
public createFileSync(path: string, flag: string, mode: number): File { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public async unlink(path: string, cred: Cred): Promise<void> { | ||
public async unlink(path: string): Promise<void> { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public unlinkSync(path: string, cred: Cred): void { | ||
public unlinkSync(path: string): void { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public async rmdir(path: string, cred: Cred): Promise<void> { | ||
public async rmdir(path: string): Promise<void> { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public rmdirSync(path: string, cred: Cred): void { | ||
public rmdirSync(path: string): void { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public async mkdir(path: string, mode: number, cred: Cred): Promise<void> { | ||
public async mkdir(path: string, mode: number): Promise<void> { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public mkdirSync(path: string, mode: number, cred: Cred): void { | ||
public mkdirSync(path: string, mode: number): void { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> { | ||
public async link(srcpath: string, dstpath: string): Promise<void> { | ||
throw new ErrnoError(Errno.EROFS); | ||
} | ||
public linkSync(srcpath: string, dstpath: string, cred: Cred): void { | ||
public linkSync(srcpath: string, dstpath: string): void { | ||
throw new ErrnoError(Errno.EROFS); | ||
@@ -87,0 +86,0 @@ } |
@@ -1,5 +0,4 @@ | ||
import type { Cred } from '../cred.js'; | ||
import type { File } from '../file.js'; | ||
import type { FileSystem } from '../filesystem.js'; | ||
import type { Stats } from '../stats.js'; | ||
import type { FileSystem } from '../filesystem.js'; | ||
import type { Mixin, _AsyncFSMethods } from './shared.js'; | ||
@@ -13,40 +12,40 @@ | ||
abstract class SyncFS extends FS implements _AsyncFSMethods { | ||
public async exists(path: string, cred: Cred): Promise<boolean> { | ||
return this.existsSync(path, cred); | ||
public async exists(path: string): Promise<boolean> { | ||
return this.existsSync(path); | ||
} | ||
public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> { | ||
return this.renameSync(oldPath, newPath, cred); | ||
public async rename(oldPath: string, newPath: string): Promise<void> { | ||
return this.renameSync(oldPath, newPath); | ||
} | ||
public async stat(path: string, cred: Cred): Promise<Stats> { | ||
return this.statSync(path, cred); | ||
public async stat(path: string): Promise<Stats> { | ||
return this.statSync(path); | ||
} | ||
public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> { | ||
return this.createFileSync(path, flag, mode, cred); | ||
public async createFile(path: string, flag: string, mode: number): Promise<File> { | ||
return this.createFileSync(path, flag, mode); | ||
} | ||
public async openFile(path: string, flag: string, cred: Cred): Promise<File> { | ||
return this.openFileSync(path, flag, cred); | ||
public async openFile(path: string, flag: string): Promise<File> { | ||
return this.openFileSync(path, flag); | ||
} | ||
public async unlink(path: string, cred: Cred): Promise<void> { | ||
return this.unlinkSync(path, cred); | ||
public async unlink(path: string): Promise<void> { | ||
return this.unlinkSync(path); | ||
} | ||
public async rmdir(path: string, cred: Cred): Promise<void> { | ||
return this.rmdirSync(path, cred); | ||
public async rmdir(path: string): Promise<void> { | ||
return this.rmdirSync(path); | ||
} | ||
public async mkdir(path: string, mode: number, cred: Cred): Promise<void> { | ||
return this.mkdirSync(path, mode, cred); | ||
public async mkdir(path: string, mode: number): Promise<void> { | ||
return this.mkdirSync(path, mode); | ||
} | ||
public async readdir(path: string, cred: Cred): Promise<string[]> { | ||
return this.readdirSync(path, cred); | ||
public async readdir(path: string): Promise<string[]> { | ||
return this.readdirSync(path); | ||
} | ||
public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> { | ||
return this.linkSync(srcpath, dstpath, cred); | ||
public async link(srcpath: string, dstpath: string): Promise<void> { | ||
return this.linkSync(srcpath, dstpath); | ||
} | ||
@@ -53,0 +52,0 @@ |
import type * as Node from 'fs'; | ||
import type { Cred } from './cred.js'; | ||
import 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'; | ||
@@ -240,3 +240,3 @@ import { size_max } from './inode.js'; | ||
*/ | ||
public hasAccess(mode: number, cred: Cred): boolean { | ||
public hasAccess(mode: number, cred: Credentials): boolean { | ||
if (cred.euid === 0 || cred.egid === 0) { | ||
@@ -256,3 +256,3 @@ //Running as root | ||
*/ | ||
public cred(uid: number = Number(this.uid), gid: number = Number(this.gid)): Cred { | ||
public cred(uid: number = Number(this.uid), gid: number = Number(this.gid)): Credentials { | ||
return { | ||
@@ -321,4 +321,2 @@ uid, | ||
* Stats with bigint | ||
* @todo Implement with bigint instead of wrapping Stats | ||
* @internal | ||
*/ | ||
@@ -330,4 +328,16 @@ export class BigIntStats extends StatsCommon<bigint> implements Node.BigIntStats, StatsLike { | ||
/** | ||
* Determines if the file stats have changed by comparing relevant properties. | ||
* | ||
* @param left The previous stats. | ||
* @param right The current stats. | ||
* @returns `true` if stats have changed; otherwise, `false`. | ||
* @internal | ||
*/ | ||
export function isStatsEqual<T extends number | bigint>(left: StatsCommon<T>, right: StatsCommon<T>): boolean { | ||
return left.size == right.size && +left.atime == +right.atime && +left.mtime == +right.mtime && +left.ctime == +right.ctime && left.mode == right.mode; | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
export const ZenFsType = 0x7a656e6673; // 'z' 'e' 'n' 'f' 's' | ||
@@ -334,0 +344,0 @@ |
@@ -1,10 +0,7 @@ | ||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ | ||
/* eslint-disable @typescript-eslint/no-unsafe-call */ | ||
/* eslint-disable @typescript-eslint/no-unsafe-return */ | ||
/* 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 { ErrnoError, Errno } from './error.js'; | ||
import type { Cred } from './cred.js'; | ||
import { dirname, resolve, type AbsolutePath } from './emulation/path.js'; | ||
import { Errno, ErrnoError } from './error.js'; | ||
import type { FileSystem } from './filesystem.js'; | ||
import type * as fs from 'node:fs'; | ||
@@ -24,6 +21,6 @@ declare global { | ||
*/ | ||
export function mkdirpSync(path: string, mode: number, cred: Cred, fs: FileSystem): void { | ||
if (!fs.existsSync(path, cred)) { | ||
mkdirpSync(dirname(path), mode, cred, fs); | ||
fs.mkdirSync(path, mode, cred); | ||
export function mkdirpSync(path: string, mode: number, fs: FileSystem): void { | ||
if (!fs.existsSync(path)) { | ||
mkdirpSync(dirname(path), mode, fs); | ||
fs.mkdirSync(path, mode); | ||
} | ||
@@ -154,9 +151,3 @@ } | ||
export function decodeDirListing(data: Uint8Array): Record<string, bigint> { | ||
return JSON.parse(decode(data), (k, v) => { | ||
if (k == '') { | ||
return v; | ||
} | ||
return BigInt(v); | ||
}); | ||
return JSON.parse(decode(data), (k, v) => (k == '' ? v : BigInt(v as string))); | ||
} | ||
@@ -169,11 +160,3 @@ | ||
export function encodeDirListing(data: Record<string, bigint>): Uint8Array { | ||
return encode( | ||
JSON.stringify(data, (k, v) => { | ||
if (k == '') { | ||
return v; | ||
} | ||
return v.toString(); | ||
}) | ||
); | ||
return encode(JSON.stringify(data, (k, v) => (k == '' ? v : v.toString()))); | ||
} | ||
@@ -180,0 +163,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
1964704
22105
+ Added@xterm/xterm@5.5.0(transitive)
+ Addedutilium@1.2.10(transitive)
- Removedutilium@0.4.4(transitive)
Updatedutilium@>=0.4.0