@zenfs/core 1.1.6 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backends/file_index.js +0 -3
- package/dist/backends/overlay.js +0 -8
- package/dist/backends/store/fs.js +4 -17
- package/dist/config.d.ts +24 -1
- package/dist/config.js +5 -0
- package/dist/devices.js +0 -12
- package/dist/emulation/cache.d.ts +21 -0
- package/dist/emulation/cache.js +36 -0
- package/dist/emulation/config.d.ts +10 -0
- package/dist/emulation/config.js +10 -0
- package/dist/emulation/promises.d.ts +9 -14
- package/dist/emulation/promises.js +71 -47
- package/dist/emulation/shared.d.ts +16 -0
- package/dist/emulation/sync.d.ts +11 -20
- package/dist/emulation/sync.js +44 -22
- package/dist/file.d.ts +1 -1
- package/dist/file.js +6 -3
- package/package.json +4 -2
- package/readme.md +1 -1
- package/scripts/test.js +19 -1
- package/src/backends/backend.ts +160 -0
- package/src/backends/fetch.ts +180 -0
- package/src/backends/file_index.ts +206 -0
- package/src/backends/memory.ts +50 -0
- package/src/backends/overlay.ts +560 -0
- package/src/backends/port/fs.ts +335 -0
- package/src/backends/port/readme.md +54 -0
- package/src/backends/port/rpc.ts +167 -0
- package/src/backends/readme.md +3 -0
- package/src/backends/store/fs.ts +700 -0
- package/src/backends/store/readme.md +9 -0
- package/src/backends/store/simple.ts +146 -0
- package/src/backends/store/store.ts +173 -0
- package/src/config.ts +185 -0
- package/src/credentials.ts +31 -0
- package/src/devices.ts +459 -0
- package/src/emulation/async.ts +834 -0
- package/src/emulation/cache.ts +44 -0
- package/src/emulation/config.ts +11 -0
- package/src/emulation/constants.ts +182 -0
- package/src/emulation/dir.ts +138 -0
- package/src/emulation/index.ts +8 -0
- package/src/emulation/path.ts +440 -0
- package/src/emulation/promises.ts +1134 -0
- package/src/emulation/shared.ts +153 -0
- package/src/emulation/streams.ts +34 -0
- package/src/emulation/sync.ts +868 -0
- package/src/emulation/watchers.ts +193 -0
- package/src/error.ts +307 -0
- package/src/file.ts +662 -0
- package/src/filesystem.ts +174 -0
- package/src/index.ts +25 -0
- package/src/inode.ts +132 -0
- package/src/mixins/async.ts +208 -0
- package/src/mixins/index.ts +5 -0
- package/src/mixins/mutexed.ts +257 -0
- package/src/mixins/readonly.ts +96 -0
- package/src/mixins/shared.ts +25 -0
- package/src/mixins/sync.ts +58 -0
- package/src/polyfills.ts +21 -0
- package/src/stats.ts +363 -0
- package/src/utils.ts +288 -0
- package/tests/common.ts +1 -11
- package/tests/fs/directory.test.ts +1 -1
- package/tests/fs/errors.test.ts +1 -1
- package/tests/fs/links.test.ts +1 -1
- package/tests/fs/open.test.ts +1 -1
- package/tests/fs/permissions.test.ts +4 -4
- package/tests/fs/readdir.test.ts +3 -3
- package/tests/fs/rename.test.ts +1 -1
- package/tests/fs/stat.test.ts +1 -1
- package/tests/fs/times.test.ts +2 -2
- package/tests/fs/truncate.test.ts +1 -1
- package/tests/port/channel.test.ts +3 -3
- package/tests/port/config.test.ts +4 -5
- package/tests/port/config.worker.js +5 -0
- package/tests/port/remote.test.ts +4 -5
- package/tests/port/remote.worker.js +5 -0
- package/tests/port/timeout.test.ts +2 -2
- package/tests/setup/common.ts +1 -1
- package/tests/setup/cow+fetch.ts +1 -1
- package/tests/port/config.worker.ts +0 -5
- package/tests/port/remote.worker.ts +0 -5
|
@@ -151,9 +151,6 @@ export class IndexFS extends Readonly(FileSystem) {
|
|
|
151
151
|
if (!stats) {
|
|
152
152
|
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
153
153
|
}
|
|
154
|
-
if (!stats.isDirectory()) {
|
|
155
|
-
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
156
|
-
}
|
|
157
154
|
const content = JSON.parse(decodeUTF8(stats.fileData));
|
|
158
155
|
if (!Array.isArray(content)) {
|
|
159
156
|
throw ErrnoError.With('ENODATA', path, 'readdir');
|
package/dist/backends/overlay.js
CHANGED
|
@@ -314,10 +314,6 @@ export class UnmutexedOverlayFS extends FileSystem {
|
|
|
314
314
|
}
|
|
315
315
|
async readdir(path) {
|
|
316
316
|
this.checkInitialized();
|
|
317
|
-
const dirStats = await this.stat(path);
|
|
318
|
-
if (!dirStats.isDirectory()) {
|
|
319
|
-
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
320
|
-
}
|
|
321
317
|
// Readdir in both, check delete log on RO file system's listing, merge, return.
|
|
322
318
|
const contents = [];
|
|
323
319
|
try {
|
|
@@ -341,10 +337,6 @@ export class UnmutexedOverlayFS extends FileSystem {
|
|
|
341
337
|
}
|
|
342
338
|
readdirSync(path) {
|
|
343
339
|
this.checkInitialized();
|
|
344
|
-
const dirStats = this.statSync(path);
|
|
345
|
-
if (!dirStats.isDirectory()) {
|
|
346
|
-
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
347
|
-
}
|
|
348
340
|
// Readdir in both, check delete log on RO file system's listing, merge, return.
|
|
349
341
|
let contents = [];
|
|
350
342
|
try {
|
|
@@ -580,16 +580,12 @@ export class StoreFS extends FileSystem {
|
|
|
580
580
|
* listing.
|
|
581
581
|
*/
|
|
582
582
|
async getDirListing(tx, inode, path) {
|
|
583
|
-
if (!inode.toStats().isDirectory()) {
|
|
584
|
-
throw ErrnoError.With('ENOTDIR', path, 'getDirListing');
|
|
585
|
-
}
|
|
586
583
|
const data = await tx.get(inode.ino);
|
|
584
|
+
/*
|
|
585
|
+
Occurs when data is undefined,or corresponds to something other than a directory listing.
|
|
586
|
+
The latter should never occur unless the file system is corrupted.
|
|
587
|
+
*/
|
|
587
588
|
if (!data) {
|
|
588
|
-
/*
|
|
589
|
-
Occurs when data is undefined, or corresponds to something other
|
|
590
|
-
than a directory listing. The latter should never occur unless
|
|
591
|
-
the file system is corrupted.
|
|
592
|
-
*/
|
|
593
589
|
throw ErrnoError.With('ENOENT', path, 'getDirListing');
|
|
594
590
|
}
|
|
595
591
|
return decodeDirListing(data);
|
|
@@ -598,9 +594,6 @@ export class StoreFS extends FileSystem {
|
|
|
598
594
|
* Given the Inode of a directory, retrieves the corresponding directory listing.
|
|
599
595
|
*/
|
|
600
596
|
getDirListingSync(tx, inode, p) {
|
|
601
|
-
if (!inode.toStats().isDirectory()) {
|
|
602
|
-
throw ErrnoError.With('ENOTDIR', p, 'getDirListing');
|
|
603
|
-
}
|
|
604
597
|
const data = tx.getSync(inode.ino);
|
|
605
598
|
if (!data) {
|
|
606
599
|
throw ErrnoError.With('ENOENT', p, 'getDirListing');
|
|
@@ -758,9 +751,6 @@ export class StoreFS extends FileSystem {
|
|
|
758
751
|
if (!isDir && fileNode.toStats().isDirectory()) {
|
|
759
752
|
throw ErrnoError.With('EISDIR', path, 'remove');
|
|
760
753
|
}
|
|
761
|
-
if (isDir && !fileNode.toStats().isDirectory()) {
|
|
762
|
-
throw ErrnoError.With('ENOTDIR', path, 'remove');
|
|
763
|
-
}
|
|
764
754
|
await tx.set(parentNode.ino, encodeDirListing(listing));
|
|
765
755
|
if (--fileNode.nlink < 1) {
|
|
766
756
|
// remove file
|
|
@@ -801,9 +791,6 @@ export class StoreFS extends FileSystem {
|
|
|
801
791
|
if (!isDir && fileNode.toStats().isDirectory()) {
|
|
802
792
|
throw ErrnoError.With('EISDIR', path, 'remove');
|
|
803
793
|
}
|
|
804
|
-
if (isDir && !fileNode.toStats().isDirectory()) {
|
|
805
|
-
throw ErrnoError.With('ENOTDIR', path, 'remove');
|
|
806
|
-
}
|
|
807
794
|
// Update directory listing.
|
|
808
795
|
tx.setSync(parentNode.ino, encodeDirListing(listing));
|
|
809
796
|
if (--fileNode.nlink < 1) {
|
package/dist/config.d.ts
CHANGED
|
@@ -34,10 +34,33 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
|
|
|
34
34
|
gid: number;
|
|
35
35
|
/**
|
|
36
36
|
* Whether to automatically add normal Linux devices
|
|
37
|
-
* @default false
|
|
38
37
|
* @experimental
|
|
38
|
+
* @default false
|
|
39
39
|
*/
|
|
40
40
|
addDevices: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* If true, enables caching stats for certain operations.
|
|
43
|
+
* This should reduce the number of stat calls performed.
|
|
44
|
+
* @experimental
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
cacheStats: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* If true, disables *all* permissions checking.
|
|
50
|
+
*
|
|
51
|
+
* This can increase performance.
|
|
52
|
+
* @experimental
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
disableAccessChecks: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* If true, disables `read` and `readSync` from immediately syncing the updated atime to the file system.
|
|
58
|
+
*
|
|
59
|
+
* This can increase performance.
|
|
60
|
+
* @experimental
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
disableSyncOnRead: boolean;
|
|
41
64
|
}
|
|
42
65
|
/**
|
|
43
66
|
* Configures ZenFS with single mount point /
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
|
|
2
2
|
import { credentials } from './credentials.js';
|
|
3
3
|
import { DeviceFS, fullDevice, nullDevice, randomDevice, zeroDevice } from './devices.js';
|
|
4
|
+
import * as cache from './emulation/cache.js';
|
|
4
5
|
import * as fs from './emulation/index.js';
|
|
6
|
+
import { config } from './emulation/config.js';
|
|
5
7
|
import { Errno, ErrnoError } from './error.js';
|
|
6
8
|
import { FileSystem } from './filesystem.js';
|
|
7
9
|
function isMountConfig(arg) {
|
|
@@ -66,6 +68,9 @@ export async function configure(configuration) {
|
|
|
66
68
|
const uid = 'uid' in configuration ? configuration.uid || 0 : 0;
|
|
67
69
|
const gid = 'gid' in configuration ? configuration.gid || 0 : 0;
|
|
68
70
|
Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
|
|
71
|
+
cache.setEnabled(configuration.cacheStats ?? false);
|
|
72
|
+
config.checkAccess = !configuration.disableAccessChecks;
|
|
73
|
+
config.syncOnRead = !configuration.disableSyncOnRead;
|
|
69
74
|
if (configuration.addDevices) {
|
|
70
75
|
const devfs = new DeviceFS();
|
|
71
76
|
devfs.createDevice('/null', nullDevice);
|
package/dist/devices.js
CHANGED
|
@@ -254,15 +254,9 @@ export class DeviceFS extends StoreFS {
|
|
|
254
254
|
return super.unlinkSync(path);
|
|
255
255
|
}
|
|
256
256
|
async rmdir(path) {
|
|
257
|
-
if (this.devices.has(path)) {
|
|
258
|
-
throw ErrnoError.With('ENOTDIR', path, 'rmdir');
|
|
259
|
-
}
|
|
260
257
|
return super.rmdir(path);
|
|
261
258
|
}
|
|
262
259
|
rmdirSync(path) {
|
|
263
|
-
if (this.devices.has(path)) {
|
|
264
|
-
throw ErrnoError.With('ENOTDIR', path, 'rmdir');
|
|
265
|
-
}
|
|
266
260
|
return super.rmdirSync(path);
|
|
267
261
|
}
|
|
268
262
|
async mkdir(path, mode) {
|
|
@@ -278,9 +272,6 @@ export class DeviceFS extends StoreFS {
|
|
|
278
272
|
return super.mkdirSync(path, mode);
|
|
279
273
|
}
|
|
280
274
|
async readdir(path) {
|
|
281
|
-
if (this.devices.has(path)) {
|
|
282
|
-
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
283
|
-
}
|
|
284
275
|
const entries = await super.readdir(path);
|
|
285
276
|
for (const dev of this.devices.keys()) {
|
|
286
277
|
if (dirname(dev) == path) {
|
|
@@ -290,9 +281,6 @@ export class DeviceFS extends StoreFS {
|
|
|
290
281
|
return entries;
|
|
291
282
|
}
|
|
292
283
|
readdirSync(path) {
|
|
293
|
-
if (this.devices.has(path)) {
|
|
294
|
-
throw ErrnoError.With('ENOTDIR', path, 'readdirSync');
|
|
295
|
-
}
|
|
296
284
|
const entries = super.readdirSync(path);
|
|
297
285
|
for (const dev of this.devices.keys()) {
|
|
298
286
|
if (dirname(dev) == path) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Stats } from '../stats.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether the cache is enabled
|
|
4
|
+
*/
|
|
5
|
+
export declare let isEnabled: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Sets whether the cache is enabled or not
|
|
8
|
+
*/
|
|
9
|
+
export declare function setEnabled(value: boolean): void;
|
|
10
|
+
/**
|
|
11
|
+
* Gets stats from the cache, if they exist and the cache is enabled.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getStats(path: string): Stats | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Adds stats if the cache is enabled
|
|
16
|
+
*/
|
|
17
|
+
export declare function setStats(path: string, value: Stats): void;
|
|
18
|
+
/**
|
|
19
|
+
* Clears the cache if it is enabled
|
|
20
|
+
*/
|
|
21
|
+
export declare function clearStats(): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* Experimental caching */
|
|
2
|
+
/**
|
|
3
|
+
* Whether the cache is enabled
|
|
4
|
+
*/
|
|
5
|
+
export let isEnabled = false;
|
|
6
|
+
/**
|
|
7
|
+
* Sets whether the cache is enabled or not
|
|
8
|
+
*/
|
|
9
|
+
export function setEnabled(value) {
|
|
10
|
+
isEnabled = value;
|
|
11
|
+
}
|
|
12
|
+
const stats = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Gets stats from the cache, if they exist and the cache is enabled.
|
|
15
|
+
*/
|
|
16
|
+
export function getStats(path) {
|
|
17
|
+
if (!isEnabled)
|
|
18
|
+
return;
|
|
19
|
+
return stats.get(path);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Adds stats if the cache is enabled
|
|
23
|
+
*/
|
|
24
|
+
export function setStats(path, value) {
|
|
25
|
+
if (!isEnabled)
|
|
26
|
+
return;
|
|
27
|
+
stats.set(path, value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Clears the cache if it is enabled
|
|
31
|
+
*/
|
|
32
|
+
export function clearStats() {
|
|
33
|
+
if (!isEnabled)
|
|
34
|
+
return;
|
|
35
|
+
stats.clear();
|
|
36
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { FileContents } from '../filesystem.js';
|
|
|
9
9
|
import '../polyfills.js';
|
|
10
10
|
import { BigIntStats, type Stats } from '../stats.js';
|
|
11
11
|
import { Dir, Dirent } from './dir.js';
|
|
12
|
+
import { type InternalOptions, type ReaddirOptions } from './shared.js';
|
|
12
13
|
import { ReadStream, WriteStream } from './streams.js';
|
|
13
14
|
export * as constants from './constants.js';
|
|
14
15
|
export declare class FileHandle implements promises.FileHandle {
|
|
@@ -248,30 +249,24 @@ export declare function mkdir(path: fs.PathLike, options?: fs.Mode | (fs.MakeDir
|
|
|
248
249
|
export declare function mkdir(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): Promise<string | undefined>;
|
|
249
250
|
/**
|
|
250
251
|
* Asynchronous readdir(3) - read a directory.
|
|
252
|
+
*
|
|
253
|
+
* Note: The order of entries is not guaranteed
|
|
251
254
|
* @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
|
|
252
255
|
* @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'`.
|
|
253
256
|
*/
|
|
254
|
-
export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & {
|
|
257
|
+
export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
|
|
255
258
|
withFileTypes?: false;
|
|
256
|
-
recursive?: boolean;
|
|
257
259
|
}) | BufferEncoding | null): Promise<string[]>;
|
|
258
|
-
export declare function readdir(path: fs.PathLike, options: fs.BufferEncodingOption & {
|
|
260
|
+
export declare function readdir(path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & {
|
|
259
261
|
withFileTypes?: false;
|
|
260
|
-
recursive?: boolean;
|
|
261
262
|
}): Promise<Buffer[]>;
|
|
262
|
-
export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & {
|
|
263
|
+
export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
|
|
263
264
|
withFileTypes?: false;
|
|
264
|
-
recursive?: boolean;
|
|
265
265
|
}) | BufferEncoding | null): Promise<string[] | Buffer[]>;
|
|
266
|
-
export declare function readdir(path: fs.PathLike, options: fs.ObjectEncodingOptions & {
|
|
266
|
+
export declare function readdir(path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & {
|
|
267
267
|
withFileTypes: true;
|
|
268
|
-
recursive?: boolean;
|
|
269
268
|
}): Promise<Dirent[]>;
|
|
270
|
-
export declare function readdir(path: fs.PathLike, options?:
|
|
271
|
-
withFileTypes?: boolean;
|
|
272
|
-
recursive?: boolean;
|
|
273
|
-
encoding?: BufferEncoding | 'buffer' | null;
|
|
274
|
-
} | BufferEncoding | 'buffer' | null): Promise<string[] | Dirent[] | Buffer[]>;
|
|
269
|
+
export declare function readdir(path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): Promise<string[] | Dirent[] | Buffer[]>;
|
|
275
270
|
export declare function link(targetPath: fs.PathLike, linkPath: fs.PathLike): Promise<void>;
|
|
276
271
|
/**
|
|
277
272
|
* `symlink`.
|
|
@@ -311,7 +306,7 @@ export declare function access(path: fs.PathLike, mode?: number): Promise<void>;
|
|
|
311
306
|
* Asynchronous `rm`. Removes files or directories (recursively).
|
|
312
307
|
* @param path The path to the file or directory to remove.
|
|
313
308
|
*/
|
|
314
|
-
export declare function rm(path: fs.PathLike, options?: fs.RmOptions): Promise<void>;
|
|
309
|
+
export declare function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOptions): Promise<void>;
|
|
315
310
|
/**
|
|
316
311
|
* Asynchronous `mkdtemp`. Creates a unique temporary directory.
|
|
317
312
|
* @param prefix The directory prefix.
|
|
@@ -52,10 +52,12 @@ import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWrit
|
|
|
52
52
|
import '../polyfills.js';
|
|
53
53
|
import { BigIntStats } from '../stats.js';
|
|
54
54
|
import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
|
|
55
|
+
import * as cache from './cache.js';
|
|
55
56
|
import * as constants from './constants.js';
|
|
56
57
|
import { Dir, Dirent } from './dir.js';
|
|
57
58
|
import { dirname, join, parse } from './path.js';
|
|
58
59
|
import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
|
|
60
|
+
import { config } from './config.js';
|
|
59
61
|
import { ReadStream, WriteStream } from './streams.js';
|
|
60
62
|
import { FSWatcher, emitChange } from './watchers.js';
|
|
61
63
|
export * as constants from './constants.js';
|
|
@@ -217,7 +219,7 @@ export class FileHandle {
|
|
|
217
219
|
}
|
|
218
220
|
async stat(opts) {
|
|
219
221
|
const stats = await this.file.stat();
|
|
220
|
-
if (!stats.hasAccess(constants.R_OK)) {
|
|
222
|
+
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
221
223
|
throw ErrnoError.With('EACCES', this.file.path, 'stat');
|
|
222
224
|
}
|
|
223
225
|
return opts?.bigint ? new BigIntStats(stats) : stats;
|
|
@@ -351,7 +353,7 @@ export async function rename(oldPath, newPath) {
|
|
|
351
353
|
newPath = normalizePath(newPath);
|
|
352
354
|
const src = resolveMount(oldPath);
|
|
353
355
|
const dst = resolveMount(newPath);
|
|
354
|
-
if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK)) {
|
|
356
|
+
if (config.checkAccess && !(await stat(dirname(oldPath))).hasAccess(constants.W_OK)) {
|
|
355
357
|
throw ErrnoError.With('EACCES', oldPath, 'rename');
|
|
356
358
|
}
|
|
357
359
|
try {
|
|
@@ -389,7 +391,7 @@ export async function stat(path, options) {
|
|
|
389
391
|
const { fs, path: resolved } = resolveMount(await realpath(path));
|
|
390
392
|
try {
|
|
391
393
|
const stats = await fs.stat(resolved);
|
|
392
|
-
if (!stats.hasAccess(constants.R_OK)) {
|
|
394
|
+
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
393
395
|
throw ErrnoError.With('EACCES', resolved, 'stat');
|
|
394
396
|
}
|
|
395
397
|
return options?.bigint ? new BigIntStats(stats) : stats;
|
|
@@ -433,7 +435,7 @@ export async function unlink(path) {
|
|
|
433
435
|
path = normalizePath(path);
|
|
434
436
|
const { fs, path: resolved } = resolveMount(path);
|
|
435
437
|
try {
|
|
436
|
-
if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) {
|
|
438
|
+
if (config.checkAccess && !(cache.getStats(path) || (await fs.stat(resolved))).hasAccess(constants.W_OK)) {
|
|
437
439
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
438
440
|
}
|
|
439
441
|
await fs.unlink(resolved);
|
|
@@ -460,7 +462,7 @@ async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
|
|
|
460
462
|
}
|
|
461
463
|
// Create the file
|
|
462
464
|
const parentStats = await fs.stat(dirname(resolved));
|
|
463
|
-
if (!parentStats.hasAccess(constants.W_OK)) {
|
|
465
|
+
if (config.checkAccess && !parentStats.hasAccess(constants.W_OK)) {
|
|
464
466
|
throw ErrnoError.With('EACCES', dirname(path), '_open');
|
|
465
467
|
}
|
|
466
468
|
if (!parentStats.isDirectory()) {
|
|
@@ -468,7 +470,7 @@ async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
|
|
|
468
470
|
}
|
|
469
471
|
return new FileHandle(await fs.createFile(resolved, flag, mode));
|
|
470
472
|
}
|
|
471
|
-
if (!stats.hasAccess(flagToMode(flag))) {
|
|
473
|
+
if (config.checkAccess && !stats.hasAccess(flagToMode(flag))) {
|
|
472
474
|
throw ErrnoError.With('EACCES', path, '_open');
|
|
473
475
|
}
|
|
474
476
|
if (isExclusive(flag)) {
|
|
@@ -579,11 +581,14 @@ export async function appendFile(path, data, _options) {
|
|
|
579
581
|
appendFile;
|
|
580
582
|
// DIRECTORY-ONLY METHODS
|
|
581
583
|
export async function rmdir(path) {
|
|
582
|
-
path = normalizePath(path);
|
|
583
584
|
path = await realpath(path);
|
|
584
585
|
const { fs, path: resolved } = resolveMount(path);
|
|
585
586
|
try {
|
|
586
|
-
|
|
587
|
+
const stats = cache.getStats(path) || (await fs.stat(resolved));
|
|
588
|
+
if (!stats.isDirectory()) {
|
|
589
|
+
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
590
|
+
}
|
|
591
|
+
if (config.checkAccess && !stats.hasAccess(constants.W_OK)) {
|
|
587
592
|
throw ErrnoError.With('EACCES', resolved, 'rmdir');
|
|
588
593
|
}
|
|
589
594
|
await fs.rmdir(resolved);
|
|
@@ -597,12 +602,12 @@ rmdir;
|
|
|
597
602
|
export async function mkdir(path, options) {
|
|
598
603
|
options = typeof options === 'object' ? options : { mode: options };
|
|
599
604
|
const mode = normalizeMode(options?.mode, 0o777);
|
|
600
|
-
path = await realpath(
|
|
605
|
+
path = await realpath(path);
|
|
601
606
|
const { fs, path: resolved } = resolveMount(path);
|
|
602
607
|
const errorPaths = { [resolved]: path };
|
|
603
608
|
try {
|
|
604
609
|
if (!options?.recursive) {
|
|
605
|
-
if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK)) {
|
|
610
|
+
if (config.checkAccess && !(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK)) {
|
|
606
611
|
throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir');
|
|
607
612
|
}
|
|
608
613
|
await fs.mkdir(resolved, mode);
|
|
@@ -615,7 +620,7 @@ export async function mkdir(path, options) {
|
|
|
615
620
|
errorPaths[dir] = origDir;
|
|
616
621
|
}
|
|
617
622
|
for (const dir of dirs) {
|
|
618
|
-
if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK)) {
|
|
623
|
+
if (config.checkAccess && !(await fs.stat(dirname(dir))).hasAccess(constants.W_OK)) {
|
|
619
624
|
throw ErrnoError.With('EACCES', dirname(dir), 'mkdir');
|
|
620
625
|
}
|
|
621
626
|
await fs.mkdir(dir, mode);
|
|
@@ -630,14 +635,20 @@ export async function mkdir(path, options) {
|
|
|
630
635
|
mkdir;
|
|
631
636
|
export async function readdir(path, options) {
|
|
632
637
|
options = typeof options === 'object' ? options : { encoding: options };
|
|
633
|
-
path = await realpath(
|
|
638
|
+
path = await realpath(path);
|
|
639
|
+
const handleError = (e) => {
|
|
640
|
+
throw fixError(e, { [resolved]: path });
|
|
641
|
+
};
|
|
634
642
|
const { fs, path: resolved } = resolveMount(path);
|
|
635
|
-
|
|
643
|
+
const stats = cache.getStats(path) || (await fs.stat(resolved).catch(handleError));
|
|
644
|
+
cache.setStats(path, stats);
|
|
645
|
+
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
636
646
|
throw ErrnoError.With('EACCES', path, 'readdir');
|
|
637
647
|
}
|
|
638
|
-
|
|
639
|
-
throw
|
|
640
|
-
}
|
|
648
|
+
if (!stats.isDirectory()) {
|
|
649
|
+
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
650
|
+
}
|
|
651
|
+
const entries = await fs.readdir(resolved).catch(handleError);
|
|
641
652
|
for (const point of mounts.keys()) {
|
|
642
653
|
if (point.startsWith(path)) {
|
|
643
654
|
const entry = point.slice(path.length);
|
|
@@ -649,26 +660,24 @@ export async function readdir(path, options) {
|
|
|
649
660
|
}
|
|
650
661
|
}
|
|
651
662
|
const values = [];
|
|
652
|
-
|
|
653
|
-
let
|
|
663
|
+
const addEntry = async (entry) => {
|
|
664
|
+
let entryStats;
|
|
654
665
|
if (options?.recursive || options?.withFileTypes) {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
});
|
|
666
|
+
entryStats = cache.getStats(join(path, entry)) || (await fs.stat(join(resolved, entry)).catch(handleError));
|
|
667
|
+
cache.setStats(join(path, entry), entryStats);
|
|
658
668
|
}
|
|
659
669
|
if (options?.withFileTypes) {
|
|
660
|
-
values.push(new Dirent(entry,
|
|
670
|
+
values.push(new Dirent(entry, entryStats));
|
|
661
671
|
}
|
|
662
|
-
else if (options?.encoding
|
|
672
|
+
else if (options?.encoding == 'buffer') {
|
|
663
673
|
values.push(Buffer.from(entry));
|
|
664
674
|
}
|
|
665
675
|
else {
|
|
666
676
|
values.push(entry);
|
|
667
677
|
}
|
|
668
|
-
if (!options?.recursive || !
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
for (const subEntry of await readdir(join(path, entry), options)) {
|
|
678
|
+
if (!options?.recursive || !entryStats?.isDirectory())
|
|
679
|
+
return;
|
|
680
|
+
for (const subEntry of await readdir(join(path, entry), { ...options, _isIndirect: true })) {
|
|
672
681
|
if (subEntry instanceof Dirent) {
|
|
673
682
|
subEntry.path = join(entry, subEntry.path);
|
|
674
683
|
values.push(subEntry);
|
|
@@ -681,26 +690,30 @@ export async function readdir(path, options) {
|
|
|
681
690
|
values.push(join(entry, subEntry));
|
|
682
691
|
}
|
|
683
692
|
}
|
|
693
|
+
};
|
|
694
|
+
await Promise.all(entries.map(addEntry));
|
|
695
|
+
if (!options?._isIndirect) {
|
|
696
|
+
cache.clearStats();
|
|
684
697
|
}
|
|
685
698
|
return values;
|
|
686
699
|
}
|
|
687
700
|
readdir;
|
|
688
701
|
export async function link(targetPath, linkPath) {
|
|
689
702
|
targetPath = normalizePath(targetPath);
|
|
690
|
-
if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK)) {
|
|
691
|
-
throw ErrnoError.With('EACCES', dirname(targetPath), 'link');
|
|
692
|
-
}
|
|
693
703
|
linkPath = normalizePath(linkPath);
|
|
694
|
-
if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK)) {
|
|
695
|
-
throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
|
|
696
|
-
}
|
|
697
704
|
const { fs, path } = resolveMount(targetPath);
|
|
698
705
|
const link = resolveMount(linkPath);
|
|
699
706
|
if (fs != link.fs) {
|
|
700
707
|
throw ErrnoError.With('EXDEV', linkPath, 'link');
|
|
701
708
|
}
|
|
702
709
|
try {
|
|
703
|
-
if (!(await fs.stat(
|
|
710
|
+
if (config.checkAccess && !(await fs.stat(dirname(targetPath))).hasAccess(constants.R_OK)) {
|
|
711
|
+
throw ErrnoError.With('EACCES', dirname(path), 'link');
|
|
712
|
+
}
|
|
713
|
+
if (config.checkAccess && !(await stat(dirname(linkPath))).hasAccess(constants.W_OK)) {
|
|
714
|
+
throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
|
|
715
|
+
}
|
|
716
|
+
if (config.checkAccess && !(await fs.stat(path)).hasAccess(constants.W_OK | constants.R_OK)) {
|
|
704
717
|
throw ErrnoError.With('EACCES', path, 'link');
|
|
705
718
|
}
|
|
706
719
|
return await fs.link(path, link.path);
|
|
@@ -907,6 +920,8 @@ export function watch(filename, options = {}) {
|
|
|
907
920
|
}
|
|
908
921
|
watch;
|
|
909
922
|
export async function access(path, mode = constants.F_OK) {
|
|
923
|
+
if (!config.checkAccess)
|
|
924
|
+
return;
|
|
910
925
|
const stats = await stat(path);
|
|
911
926
|
if (!stats.hasAccess(mode)) {
|
|
912
927
|
throw new ErrnoError(Errno.EACCES);
|
|
@@ -919,33 +934,39 @@ access;
|
|
|
919
934
|
*/
|
|
920
935
|
export async function rm(path, options) {
|
|
921
936
|
path = normalizePath(path);
|
|
922
|
-
const stats =
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
937
|
+
const stats = cache.getStats(path) ||
|
|
938
|
+
(await stat(path).catch((error) => {
|
|
939
|
+
if (error.code != 'ENOENT' || !options?.force)
|
|
940
|
+
throw error;
|
|
941
|
+
}));
|
|
926
942
|
if (!stats) {
|
|
927
943
|
return;
|
|
928
944
|
}
|
|
945
|
+
cache.setStats(path, stats);
|
|
929
946
|
switch (stats.mode & constants.S_IFMT) {
|
|
930
947
|
case constants.S_IFDIR:
|
|
931
948
|
if (options?.recursive) {
|
|
932
|
-
for (const entry of await readdir(path)) {
|
|
933
|
-
await rm(join(path, entry), options);
|
|
949
|
+
for (const entry of await readdir(path, { _isIndirect: true })) {
|
|
950
|
+
await rm(join(path, entry), { ...options, _isIndirect: true });
|
|
934
951
|
}
|
|
935
952
|
}
|
|
936
953
|
await rmdir(path);
|
|
937
|
-
|
|
954
|
+
break;
|
|
938
955
|
case constants.S_IFREG:
|
|
939
956
|
case constants.S_IFLNK:
|
|
940
957
|
await unlink(path);
|
|
941
|
-
|
|
958
|
+
break;
|
|
942
959
|
case constants.S_IFBLK:
|
|
943
960
|
case constants.S_IFCHR:
|
|
944
961
|
case constants.S_IFIFO:
|
|
945
962
|
case constants.S_IFSOCK:
|
|
946
963
|
default:
|
|
964
|
+
cache.clearStats();
|
|
947
965
|
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
|
|
948
966
|
}
|
|
967
|
+
if (!options?._isIndirect) {
|
|
968
|
+
cache.clearStats();
|
|
969
|
+
}
|
|
949
970
|
}
|
|
950
971
|
rm;
|
|
951
972
|
export async function mkdtemp(prefix, options) {
|
|
@@ -1005,18 +1026,21 @@ export async function cp(source, destination, opts) {
|
|
|
1005
1026
|
throw new ErrnoError(Errno.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
|
|
1006
1027
|
}
|
|
1007
1028
|
switch (srcStats.mode & constants.S_IFMT) {
|
|
1008
|
-
case constants.S_IFDIR:
|
|
1029
|
+
case constants.S_IFDIR: {
|
|
1009
1030
|
if (!opts?.recursive) {
|
|
1010
1031
|
throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp');
|
|
1011
1032
|
}
|
|
1012
|
-
await mkdir(destination, { recursive: true })
|
|
1013
|
-
|
|
1033
|
+
const [entries] = await Promise.all([readdir(source, { withFileTypes: true }), mkdir(destination, { recursive: true })] // Ensure the destination directory exists
|
|
1034
|
+
);
|
|
1035
|
+
const _cp = async (dirent) => {
|
|
1014
1036
|
if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) {
|
|
1015
|
-
|
|
1037
|
+
return; // Skip if the filter returns false
|
|
1016
1038
|
}
|
|
1017
1039
|
await cp(join(source, dirent.name), join(destination, dirent.name), opts);
|
|
1018
|
-
}
|
|
1040
|
+
};
|
|
1041
|
+
await Promise.all(entries.map(_cp));
|
|
1019
1042
|
break;
|
|
1043
|
+
}
|
|
1020
1044
|
case constants.S_IFREG:
|
|
1021
1045
|
case constants.S_IFLNK:
|
|
1022
1046
|
await copyFile(source, destination);
|
|
@@ -43,3 +43,19 @@ export declare function mountObject(mounts: MountObject): void;
|
|
|
43
43
|
* @hidden
|
|
44
44
|
*/
|
|
45
45
|
export declare function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T extends true ? BigIntStatsFs : StatsFs;
|
|
46
|
+
/**
|
|
47
|
+
* Options used for caching, among other things.
|
|
48
|
+
* @internal *UNSTABLE*
|
|
49
|
+
*/
|
|
50
|
+
export interface InternalOptions {
|
|
51
|
+
/**
|
|
52
|
+
* If true, then this readdir was called from another function.
|
|
53
|
+
* In this case, don't clear the cache when done.
|
|
54
|
+
* @internal *UNSTABLE*
|
|
55
|
+
*/
|
|
56
|
+
_isIndirect?: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface ReaddirOptions extends InternalOptions {
|
|
59
|
+
withFileTypes?: boolean;
|
|
60
|
+
recursive?: boolean;
|
|
61
|
+
}
|