@zenfs/core 1.3.4 → 1.3.6
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/config.d.ts +5 -3
- package/dist/config.js +2 -4
- package/dist/emulation/cache.d.ts +10 -6
- package/dist/emulation/cache.js +14 -9
- package/dist/emulation/promises.js +24 -17
- package/dist/emulation/shared.js +3 -0
- package/dist/emulation/sync.js +27 -19
- package/dist/stats.js +1 -3
- package/package.json +4 -2
- package/readme.md +2 -0
- package/scripts/make-index.js +24 -7
- package/src/config.ts +7 -7
- package/src/emulation/cache.ts +15 -10
- package/src/emulation/promises.ts +23 -17
- package/src/emulation/shared.ts +3 -0
- package/src/emulation/sync.ts +26 -19
- package/src/stats.ts +1 -4
- package/tests/fs/links.test.ts +4 -0
- package/tests/fs/watch.test.ts +15 -4
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js';
|
|
2
|
-
import type { AbsolutePath } from './emulation/path.js';
|
|
3
2
|
/**
|
|
4
3
|
* Configuration for a specific mount point
|
|
5
4
|
*/
|
|
@@ -9,8 +8,11 @@ export type MountConfiguration<T extends Backend> = FilesystemOf<T> | BackendCon
|
|
|
9
8
|
* @see MountConfiguration
|
|
10
9
|
*/
|
|
11
10
|
export declare function resolveMountConfig<T extends Backend>(configuration: MountConfiguration<T>, _depth?: number): Promise<FilesystemOf<T>>;
|
|
11
|
+
/**
|
|
12
|
+
* An object mapping mount points to backends
|
|
13
|
+
*/
|
|
12
14
|
export interface ConfigMounts {
|
|
13
|
-
[K:
|
|
15
|
+
[K: string]: Backend;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* Configuration
|
|
@@ -20,7 +22,7 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
|
|
|
20
22
|
* An object mapping mount points to mount configuration
|
|
21
23
|
*/
|
|
22
24
|
mounts: {
|
|
23
|
-
[K in keyof T
|
|
25
|
+
[K in keyof T]: MountConfiguration<T[K]>;
|
|
24
26
|
};
|
|
25
27
|
/**
|
|
26
28
|
* The uid to use
|
package/dist/config.js
CHANGED
|
@@ -96,10 +96,8 @@ export async function configure(configuration) {
|
|
|
96
96
|
if (configuration.mounts) {
|
|
97
97
|
const toMount = [];
|
|
98
98
|
let unmountRoot = false;
|
|
99
|
-
for (const [
|
|
100
|
-
|
|
101
|
-
throw new ErrnoError(Errno.EINVAL, 'Mount points must have absolute paths');
|
|
102
|
-
}
|
|
99
|
+
for (const [_point, mountConfig] of Object.entries(configuration.mounts)) {
|
|
100
|
+
const point = _point.startsWith('/') ? _point : '/' + _point;
|
|
103
101
|
if (isBackendConfig(mountConfig)) {
|
|
104
102
|
mountConfig.disableAsyncCache ?? (mountConfig.disableAsyncCache = configuration.disableAsyncCache || false);
|
|
105
103
|
}
|
|
@@ -7,26 +7,30 @@ export declare class Cache<T> {
|
|
|
7
7
|
isEnabled: boolean;
|
|
8
8
|
protected sync: Map<string, T>;
|
|
9
9
|
protected async: Map<string, Promise<T>>;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the data exists in the cache
|
|
12
|
+
*/
|
|
13
|
+
has(path: string): boolean;
|
|
10
14
|
/**
|
|
11
15
|
* Gets data from the cache, if is exists and the cache is enabled.
|
|
12
16
|
*/
|
|
13
|
-
|
|
17
|
+
get(path: string): T | undefined;
|
|
14
18
|
/**
|
|
15
19
|
* Adds data if the cache is enabled
|
|
16
20
|
*/
|
|
17
|
-
|
|
21
|
+
set(path: string, value: T): void;
|
|
18
22
|
/**
|
|
19
|
-
*
|
|
23
|
+
* Whether the data exists in the cache
|
|
20
24
|
*/
|
|
21
|
-
|
|
25
|
+
hasAsync(path: string): boolean;
|
|
22
26
|
/**
|
|
23
27
|
* Gets data from the cache, if it exists and the cache is enabled.
|
|
24
28
|
*/
|
|
25
|
-
|
|
29
|
+
getAsync(path: string): Promise<T> | undefined;
|
|
26
30
|
/**
|
|
27
31
|
* Adds data if the cache is enabled
|
|
28
32
|
*/
|
|
29
|
-
|
|
33
|
+
setAsync(path: string, value: Promise<T>): void;
|
|
30
34
|
/**
|
|
31
35
|
* Clears the cache if it is enabled
|
|
32
36
|
*/
|
package/dist/emulation/cache.js
CHANGED
|
@@ -9,10 +9,16 @@ export class Cache {
|
|
|
9
9
|
this.sync = new Map();
|
|
10
10
|
this.async = new Map();
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Whether the data exists in the cache
|
|
14
|
+
*/
|
|
15
|
+
has(path) {
|
|
16
|
+
return this.isEnabled && this.sync.has(path);
|
|
17
|
+
}
|
|
12
18
|
/**
|
|
13
19
|
* Gets data from the cache, if is exists and the cache is enabled.
|
|
14
20
|
*/
|
|
15
|
-
|
|
21
|
+
get(path) {
|
|
16
22
|
if (!this.isEnabled)
|
|
17
23
|
return;
|
|
18
24
|
return this.sync.get(path);
|
|
@@ -20,24 +26,22 @@ export class Cache {
|
|
|
20
26
|
/**
|
|
21
27
|
* Adds data if the cache is enabled
|
|
22
28
|
*/
|
|
23
|
-
|
|
29
|
+
set(path, value) {
|
|
24
30
|
if (!this.isEnabled)
|
|
25
31
|
return;
|
|
26
32
|
this.sync.set(path, value);
|
|
27
33
|
this.async.set(path, Promise.resolve(value));
|
|
28
34
|
}
|
|
29
35
|
/**
|
|
30
|
-
*
|
|
36
|
+
* Whether the data exists in the cache
|
|
31
37
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return;
|
|
35
|
-
this.sync.clear();
|
|
38
|
+
hasAsync(path) {
|
|
39
|
+
return this.isEnabled && this.async.has(path);
|
|
36
40
|
}
|
|
37
41
|
/**
|
|
38
42
|
* Gets data from the cache, if it exists and the cache is enabled.
|
|
39
43
|
*/
|
|
40
|
-
|
|
44
|
+
getAsync(path) {
|
|
41
45
|
if (!this.isEnabled)
|
|
42
46
|
return;
|
|
43
47
|
return this.async.get(path);
|
|
@@ -45,7 +49,7 @@ export class Cache {
|
|
|
45
49
|
/**
|
|
46
50
|
* Adds data if the cache is enabled
|
|
47
51
|
*/
|
|
48
|
-
|
|
52
|
+
setAsync(path, value) {
|
|
49
53
|
if (!this.isEnabled)
|
|
50
54
|
return;
|
|
51
55
|
this.async.set(path, value);
|
|
@@ -57,6 +61,7 @@ export class Cache {
|
|
|
57
61
|
clear() {
|
|
58
62
|
if (!this.isEnabled)
|
|
59
63
|
return;
|
|
64
|
+
this.sync.clear();
|
|
60
65
|
this.async.clear();
|
|
61
66
|
}
|
|
62
67
|
}
|
|
@@ -56,7 +56,7 @@ import * as cache from './cache.js';
|
|
|
56
56
|
import { config } from './config.js';
|
|
57
57
|
import * as constants from './constants.js';
|
|
58
58
|
import { Dir, Dirent } from './dir.js';
|
|
59
|
-
import { dirname, join, parse } from './path.js';
|
|
59
|
+
import { dirname, join, parse, resolve } from './path.js';
|
|
60
60
|
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount } from './shared.js';
|
|
61
61
|
import { ReadStream, WriteStream } from './streams.js';
|
|
62
62
|
import { FSWatcher, emitChange } from './watchers.js';
|
|
@@ -360,6 +360,7 @@ export async function rename(oldPath, newPath) {
|
|
|
360
360
|
if (src.mountPoint == dst.mountPoint) {
|
|
361
361
|
await src.fs.rename(src.path, dst.path);
|
|
362
362
|
emitChange('rename', oldPath.toString());
|
|
363
|
+
emitChange('change', newPath.toString());
|
|
363
364
|
return;
|
|
364
365
|
}
|
|
365
366
|
await writeFile(newPath, await readFile(oldPath));
|
|
@@ -435,7 +436,7 @@ export async function unlink(path) {
|
|
|
435
436
|
path = normalizePath(path);
|
|
436
437
|
const { fs, path: resolved } = resolveMount(path);
|
|
437
438
|
try {
|
|
438
|
-
if (config.checkAccess && !(await (cache.stats.
|
|
439
|
+
if (config.checkAccess && !(await (cache.stats.getAsync(path) || fs.stat(resolved))).hasAccess(constants.W_OK)) {
|
|
439
440
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
440
441
|
}
|
|
441
442
|
await fs.unlink(resolved);
|
|
@@ -583,7 +584,7 @@ export async function rmdir(path) {
|
|
|
583
584
|
path = await realpath(path);
|
|
584
585
|
const { fs, path: resolved } = resolveMount(path);
|
|
585
586
|
try {
|
|
586
|
-
const stats = await (cache.stats.
|
|
587
|
+
const stats = await (cache.stats.getAsync(path) || fs.stat(resolved));
|
|
587
588
|
if (!stats) {
|
|
588
589
|
throw ErrnoError.With('ENOENT', path, 'rmdir');
|
|
589
590
|
}
|
|
@@ -642,8 +643,8 @@ export async function readdir(path, options) {
|
|
|
642
643
|
throw fixError(e, { [resolved]: path });
|
|
643
644
|
};
|
|
644
645
|
const { fs, path: resolved } = resolveMount(path);
|
|
645
|
-
const _stats = cache.stats.
|
|
646
|
-
cache.stats.
|
|
646
|
+
const _stats = cache.stats.getAsync(path) || fs.stat(resolved).catch(handleError);
|
|
647
|
+
cache.stats.setAsync(path, _stats);
|
|
647
648
|
const stats = await _stats;
|
|
648
649
|
if (!stats) {
|
|
649
650
|
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
@@ -659,8 +660,8 @@ export async function readdir(path, options) {
|
|
|
659
660
|
const addEntry = async (entry) => {
|
|
660
661
|
let entryStats;
|
|
661
662
|
if (options?.recursive || options?.withFileTypes) {
|
|
662
|
-
const _entryStats = cache.stats.
|
|
663
|
-
cache.stats.
|
|
663
|
+
const _entryStats = cache.stats.getAsync(join(path, entry)) || fs.stat(join(resolved, entry)).catch(handleError);
|
|
664
|
+
cache.stats.setAsync(join(path, entry), _entryStats);
|
|
664
665
|
entryStats = await _entryStats;
|
|
665
666
|
}
|
|
666
667
|
if (options?.withFileTypes) {
|
|
@@ -880,17 +881,23 @@ export async function lutimes(path, atime, mtime) {
|
|
|
880
881
|
lutimes;
|
|
881
882
|
export async function realpath(path, options) {
|
|
882
883
|
path = normalizePath(path);
|
|
884
|
+
if (cache.paths.hasAsync(path))
|
|
885
|
+
return cache.paths.getAsync(path);
|
|
883
886
|
const { base, dir } = parse(path);
|
|
884
|
-
const
|
|
885
|
-
const
|
|
887
|
+
const realDir = dir == '/' ? '/' : await (cache.paths.getAsync(dir) || realpath(dir));
|
|
888
|
+
const lpath = join(realDir, base);
|
|
889
|
+
const { fs, path: resolvedPath } = resolveMount(lpath);
|
|
886
890
|
try {
|
|
887
|
-
const _stats = cache.stats.
|
|
888
|
-
cache.stats.
|
|
891
|
+
const _stats = cache.stats.getAsync(lpath) || fs.stat(resolvedPath);
|
|
892
|
+
cache.stats.setAsync(lpath, _stats);
|
|
889
893
|
if (!(await _stats).isSymbolicLink()) {
|
|
894
|
+
cache.paths.set(path, lpath);
|
|
890
895
|
return lpath;
|
|
891
896
|
}
|
|
892
|
-
const target =
|
|
893
|
-
|
|
897
|
+
const target = resolve(realDir, await readlink(lpath));
|
|
898
|
+
const real = cache.paths.getAsync(target) || realpath(target);
|
|
899
|
+
cache.paths.setAsync(path, real);
|
|
900
|
+
return await real;
|
|
894
901
|
}
|
|
895
902
|
catch (e) {
|
|
896
903
|
if (e.code == 'ENOENT') {
|
|
@@ -945,7 +952,7 @@ access;
|
|
|
945
952
|
*/
|
|
946
953
|
export async function rm(path, options) {
|
|
947
954
|
path = normalizePath(path);
|
|
948
|
-
const stats = await (cache.stats.
|
|
955
|
+
const stats = await (cache.stats.getAsync(path) ||
|
|
949
956
|
stat(path).catch((error) => {
|
|
950
957
|
if (error.code == 'ENOENT' && options?.force)
|
|
951
958
|
return undefined;
|
|
@@ -954,7 +961,7 @@ export async function rm(path, options) {
|
|
|
954
961
|
if (!stats) {
|
|
955
962
|
return;
|
|
956
963
|
}
|
|
957
|
-
cache.stats.
|
|
964
|
+
cache.stats.set(path, stats);
|
|
958
965
|
switch (stats.mode & constants.S_IFMT) {
|
|
959
966
|
case constants.S_IFDIR:
|
|
960
967
|
if (options?.recursive) {
|
|
@@ -966,10 +973,10 @@ export async function rm(path, options) {
|
|
|
966
973
|
break;
|
|
967
974
|
case constants.S_IFREG:
|
|
968
975
|
case constants.S_IFLNK:
|
|
969
|
-
await unlink(path);
|
|
970
|
-
break;
|
|
971
976
|
case constants.S_IFBLK:
|
|
972
977
|
case constants.S_IFCHR:
|
|
978
|
+
await unlink(path);
|
|
979
|
+
break;
|
|
973
980
|
case constants.S_IFIFO:
|
|
974
981
|
case constants.S_IFSOCK:
|
|
975
982
|
default:
|
package/dist/emulation/shared.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Errno, ErrnoError } from '../error.js';
|
|
|
4
4
|
import { normalizePath } from '../utils.js';
|
|
5
5
|
import { resolve } from './path.js';
|
|
6
6
|
import { size_max } from './constants.js';
|
|
7
|
+
import { paths as pathCache } from './cache.js';
|
|
7
8
|
// descriptors
|
|
8
9
|
export const fdMap = new Map();
|
|
9
10
|
let nextFd = 100;
|
|
@@ -37,6 +38,7 @@ export function mount(mountPoint, fs) {
|
|
|
37
38
|
throw new ErrnoError(Errno.EINVAL, 'Mount point ' + mountPoint + ' is already in use.');
|
|
38
39
|
}
|
|
39
40
|
mounts.set(mountPoint, fs);
|
|
41
|
+
pathCache.clear();
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
42
44
|
* Unmounts the file system at `mountPoint`.
|
|
@@ -50,6 +52,7 @@ export function umount(mountPoint) {
|
|
|
50
52
|
throw new ErrnoError(Errno.EINVAL, 'Mount point ' + mountPoint + ' is already unmounted.');
|
|
51
53
|
}
|
|
52
54
|
mounts.delete(mountPoint);
|
|
55
|
+
pathCache.clear();
|
|
53
56
|
}
|
|
54
57
|
/**
|
|
55
58
|
* Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
|
package/dist/emulation/sync.js
CHANGED
|
@@ -54,7 +54,7 @@ import * as cache from './cache.js';
|
|
|
54
54
|
import { config } from './config.js';
|
|
55
55
|
import * as constants from './constants.js';
|
|
56
56
|
import { Dir, Dirent } from './dir.js';
|
|
57
|
-
import { dirname, join, parse } from './path.js';
|
|
57
|
+
import { dirname, join, parse, resolve } from './path.js';
|
|
58
58
|
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount } from './shared.js';
|
|
59
59
|
import { emitChange } from './watchers.js';
|
|
60
60
|
export function renameSync(oldPath, newPath) {
|
|
@@ -69,6 +69,7 @@ export function renameSync(oldPath, newPath) {
|
|
|
69
69
|
if (oldMount === newMount) {
|
|
70
70
|
oldMount.fs.renameSync(oldMount.path, newMount.path);
|
|
71
71
|
emitChange('rename', oldPath.toString());
|
|
72
|
+
emitChange('change', newPath.toString());
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
75
|
writeFileSync(newPath, readFileSync(oldPath));
|
|
@@ -147,7 +148,7 @@ export function unlinkSync(path) {
|
|
|
147
148
|
path = normalizePath(path);
|
|
148
149
|
const { fs, path: resolved } = resolveMount(path);
|
|
149
150
|
try {
|
|
150
|
-
if (config.checkAccess && !(cache.stats.
|
|
151
|
+
if (config.checkAccess && !(cache.stats.get(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
|
|
151
152
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
152
153
|
}
|
|
153
154
|
fs.unlinkSync(resolved);
|
|
@@ -393,7 +394,7 @@ export function rmdirSync(path) {
|
|
|
393
394
|
path = normalizePath(path);
|
|
394
395
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
395
396
|
try {
|
|
396
|
-
const stats = cache.stats.
|
|
397
|
+
const stats = cache.stats.get(path) || fs.statSync(resolved);
|
|
397
398
|
if (!stats.isDirectory()) {
|
|
398
399
|
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
399
400
|
}
|
|
@@ -446,8 +447,8 @@ export function readdirSync(path, options) {
|
|
|
446
447
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
447
448
|
let entries;
|
|
448
449
|
try {
|
|
449
|
-
const stats = cache.stats.
|
|
450
|
-
cache.stats.
|
|
450
|
+
const stats = cache.stats.get(path) || fs.statSync(resolved);
|
|
451
|
+
cache.stats.set(path, stats);
|
|
451
452
|
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
452
453
|
throw ErrnoError.With('EACCES', resolved, 'readdir');
|
|
453
454
|
}
|
|
@@ -462,8 +463,8 @@ export function readdirSync(path, options) {
|
|
|
462
463
|
// Iterate over entries and handle recursive case if needed
|
|
463
464
|
const values = [];
|
|
464
465
|
for (const entry of entries) {
|
|
465
|
-
const entryStat = cache.stats.
|
|
466
|
-
cache.stats.
|
|
466
|
+
const entryStat = cache.stats.get(join(path, entry)) || fs.statSync(join(resolved, entry));
|
|
467
|
+
cache.stats.set(join(path, entry), entryStat);
|
|
467
468
|
if (options?.withFileTypes) {
|
|
468
469
|
values.push(new Dirent(entry, entryStat));
|
|
469
470
|
}
|
|
@@ -489,7 +490,7 @@ export function readdirSync(path, options) {
|
|
|
489
490
|
}
|
|
490
491
|
}
|
|
491
492
|
if (!options?._isIndirect) {
|
|
492
|
-
cache.stats.
|
|
493
|
+
cache.stats.clear();
|
|
493
494
|
}
|
|
494
495
|
return values;
|
|
495
496
|
}
|
|
@@ -592,16 +593,23 @@ export function lutimesSync(path, atime, mtime) {
|
|
|
592
593
|
lutimesSync;
|
|
593
594
|
export function realpathSync(path, options) {
|
|
594
595
|
path = normalizePath(path);
|
|
596
|
+
if (cache.paths.has(path))
|
|
597
|
+
return cache.paths.get(path);
|
|
595
598
|
const { base, dir } = parse(path);
|
|
596
|
-
const
|
|
597
|
-
const
|
|
599
|
+
const realDir = dir == '/' ? '/' : cache.paths.get(dir) || realpathSync(dir);
|
|
600
|
+
const lpath = join(realDir, base);
|
|
601
|
+
const { fs, path: resolvedPath } = resolveMount(lpath);
|
|
598
602
|
try {
|
|
599
|
-
const stats = fs.statSync(resolvedPath);
|
|
603
|
+
const stats = cache.stats.get(lpath) || fs.statSync(resolvedPath);
|
|
604
|
+
cache.stats.set(lpath, stats);
|
|
600
605
|
if (!stats.isSymbolicLink()) {
|
|
606
|
+
cache.paths.set(path, lpath);
|
|
601
607
|
return lpath;
|
|
602
608
|
}
|
|
603
|
-
const target =
|
|
604
|
-
|
|
609
|
+
const target = resolve(realDir, readlinkSync(lpath, options).toString());
|
|
610
|
+
const real = cache.paths.get(target) || realpathSync(target);
|
|
611
|
+
cache.paths.set(path, real);
|
|
612
|
+
return real;
|
|
605
613
|
}
|
|
606
614
|
catch (e) {
|
|
607
615
|
if (e.code == 'ENOENT') {
|
|
@@ -627,7 +635,7 @@ export function rmSync(path, options) {
|
|
|
627
635
|
path = normalizePath(path);
|
|
628
636
|
let stats;
|
|
629
637
|
try {
|
|
630
|
-
stats = cache.stats.
|
|
638
|
+
stats = cache.stats.get(path) || statSync(path);
|
|
631
639
|
}
|
|
632
640
|
catch (error) {
|
|
633
641
|
if (error.code != 'ENOENT' || !options?.force)
|
|
@@ -636,7 +644,7 @@ export function rmSync(path, options) {
|
|
|
636
644
|
if (!stats) {
|
|
637
645
|
return;
|
|
638
646
|
}
|
|
639
|
-
cache.stats.
|
|
647
|
+
cache.stats.set(path, stats);
|
|
640
648
|
switch (stats.mode & constants.S_IFMT) {
|
|
641
649
|
case constants.S_IFDIR:
|
|
642
650
|
if (options?.recursive) {
|
|
@@ -648,18 +656,18 @@ export function rmSync(path, options) {
|
|
|
648
656
|
break;
|
|
649
657
|
case constants.S_IFREG:
|
|
650
658
|
case constants.S_IFLNK:
|
|
651
|
-
unlinkSync(path);
|
|
652
|
-
break;
|
|
653
659
|
case constants.S_IFBLK:
|
|
654
660
|
case constants.S_IFCHR:
|
|
661
|
+
unlinkSync(path);
|
|
662
|
+
break;
|
|
655
663
|
case constants.S_IFIFO:
|
|
656
664
|
case constants.S_IFSOCK:
|
|
657
665
|
default:
|
|
658
|
-
cache.stats.
|
|
666
|
+
cache.stats.clear();
|
|
659
667
|
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
|
|
660
668
|
}
|
|
661
669
|
if (!options?._isIndirect) {
|
|
662
|
-
cache.stats.
|
|
670
|
+
cache.stats.clear();
|
|
663
671
|
}
|
|
664
672
|
}
|
|
665
673
|
rmSync;
|
package/dist/stats.js
CHANGED
|
@@ -109,10 +109,8 @@ export class StatsCommon {
|
|
|
109
109
|
* @internal
|
|
110
110
|
*/
|
|
111
111
|
hasAccess(mode) {
|
|
112
|
-
if (credentials.euid === 0 || credentials.egid === 0)
|
|
113
|
-
// Running as root
|
|
112
|
+
if (this.isSymbolicLink() || credentials.euid === 0 || credentials.egid === 0)
|
|
114
113
|
return true;
|
|
115
|
-
}
|
|
116
114
|
let perm = 0;
|
|
117
115
|
// Owner permissions
|
|
118
116
|
if (credentials.uid === this.uid) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -74,10 +74,12 @@
|
|
|
74
74
|
"@types/readable-stream": "^4.0.10",
|
|
75
75
|
"buffer": "^6.0.3",
|
|
76
76
|
"eventemitter3": "^5.0.1",
|
|
77
|
-
"minimatch": "^9.0.3",
|
|
78
77
|
"readable-stream": "^4.5.2",
|
|
79
78
|
"utilium": "^1.0.0"
|
|
80
79
|
},
|
|
80
|
+
"optionalDependencies": {
|
|
81
|
+
"minimatch": "^9.0.3"
|
|
82
|
+
},
|
|
81
83
|
"devDependencies": {
|
|
82
84
|
"@eslint/js": "^9.8.0",
|
|
83
85
|
"@types/eslint__js": "^8.42.3",
|
package/readme.md
CHANGED
package/scripts/make-index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import
|
|
4
|
-
import { join, relative, resolve } from 'node:path/posix';
|
|
3
|
+
import _path from 'node:path/posix';
|
|
5
4
|
import { parseArgs } from 'node:util';
|
|
6
5
|
|
|
7
6
|
const { values: options, positionals } = parseArgs({
|
|
@@ -37,6 +36,24 @@ if (options.quiet && options.verbose) {
|
|
|
37
36
|
process.exit();
|
|
38
37
|
}
|
|
39
38
|
|
|
39
|
+
let matchesGlob = _path.matchesGlob;
|
|
40
|
+
|
|
41
|
+
if (matchesGlob && options.verbose) {
|
|
42
|
+
console.debug('[debug] path.matchesGlob is available.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!matchesGlob) {
|
|
46
|
+
console.warn('Warning: path.matchesGlob is not available, falling back to minimatch. (Node 20.17.0+ or 22.5.0+ needed)');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const { minimatch } = await import('minimatch');
|
|
50
|
+
matchesGlob = minimatch;
|
|
51
|
+
} catch {
|
|
52
|
+
console.error('Fatal error: Failed to fall back to minimatch (is it installed?)');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
function fixSlash(path) {
|
|
41
58
|
return path.replaceAll('\\', '/');
|
|
42
59
|
}
|
|
@@ -71,7 +88,7 @@ const entries = new Map();
|
|
|
71
88
|
|
|
72
89
|
function computeEntries(path) {
|
|
73
90
|
try {
|
|
74
|
-
if (options.ignore.some(pattern =>
|
|
91
|
+
if (options.ignore.some(pattern => matchesGlob(path, pattern))) {
|
|
75
92
|
if (!options.quiet) console.log(`${color('yellow', 'skip')} ${path}`);
|
|
76
93
|
return;
|
|
77
94
|
}
|
|
@@ -79,7 +96,7 @@ function computeEntries(path) {
|
|
|
79
96
|
const stats = statSync(path);
|
|
80
97
|
|
|
81
98
|
if (stats.isFile()) {
|
|
82
|
-
entries.set('/' + relative(resolvedRoot, path), stats);
|
|
99
|
+
entries.set('/' + _path.relative(resolvedRoot, path), stats);
|
|
83
100
|
if (options.verbose) {
|
|
84
101
|
console.log(`${color('green', 'file')} ${path}`);
|
|
85
102
|
}
|
|
@@ -87,9 +104,9 @@ function computeEntries(path) {
|
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
for (const file of readdirSync(path)) {
|
|
90
|
-
computeEntries(join(path, file));
|
|
107
|
+
computeEntries(_path.join(path, file));
|
|
91
108
|
}
|
|
92
|
-
entries.set('/' + relative(resolvedRoot, path), stats);
|
|
109
|
+
entries.set('/' + _path.relative(resolvedRoot, path), stats);
|
|
93
110
|
if (options.verbose) {
|
|
94
111
|
console.log(`${color('bright_green', ' dir')} ${path}`);
|
|
95
112
|
}
|
|
@@ -102,7 +119,7 @@ function computeEntries(path) {
|
|
|
102
119
|
|
|
103
120
|
computeEntries(resolvedRoot);
|
|
104
121
|
if (!options.quiet) {
|
|
105
|
-
console.log('Generated listing for ' + fixSlash(resolve(root)));
|
|
122
|
+
console.log('Generated listing for ' + fixSlash(_path.resolve(root)));
|
|
106
123
|
}
|
|
107
124
|
|
|
108
125
|
const index = {
|
package/src/config.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { DeviceFS } from './devices.js';
|
|
|
5
5
|
import * as cache from './emulation/cache.js';
|
|
6
6
|
import { config } from './emulation/config.js';
|
|
7
7
|
import * as fs from './emulation/index.js';
|
|
8
|
-
import type { AbsolutePath } from './emulation/path.js';
|
|
9
8
|
import { Errno, ErrnoError } from './error.js';
|
|
10
9
|
import { FileSystem } from './filesystem.js';
|
|
11
10
|
|
|
@@ -68,8 +67,11 @@ export async function resolveMountConfig<T extends Backend>(configuration: Mount
|
|
|
68
67
|
return mount;
|
|
69
68
|
}
|
|
70
69
|
|
|
70
|
+
/**
|
|
71
|
+
* An object mapping mount points to backends
|
|
72
|
+
*/
|
|
71
73
|
export interface ConfigMounts {
|
|
72
|
-
[K:
|
|
74
|
+
[K: string]: Backend;
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/**
|
|
@@ -79,7 +81,7 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
|
|
|
79
81
|
/**
|
|
80
82
|
* An object mapping mount points to mount configuration
|
|
81
83
|
*/
|
|
82
|
-
mounts: { [K in keyof T
|
|
84
|
+
mounts: { [K in keyof T]: MountConfiguration<T[K]> };
|
|
83
85
|
|
|
84
86
|
/**
|
|
85
87
|
* The uid to use
|
|
@@ -200,10 +202,8 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
|
|
|
200
202
|
const toMount: [string, FileSystem][] = [];
|
|
201
203
|
let unmountRoot = false;
|
|
202
204
|
|
|
203
|
-
for (const [
|
|
204
|
-
|
|
205
|
-
throw new ErrnoError(Errno.EINVAL, 'Mount points must have absolute paths');
|
|
206
|
-
}
|
|
205
|
+
for (const [_point, mountConfig] of Object.entries(configuration.mounts)) {
|
|
206
|
+
const point = _point.startsWith('/') ? _point : '/' + _point;
|
|
207
207
|
|
|
208
208
|
if (isBackendConfig(mountConfig)) {
|
|
209
209
|
mountConfig.disableAsyncCache ??= configuration.disableAsyncCache || false;
|
package/src/emulation/cache.ts
CHANGED
|
@@ -13,10 +13,17 @@ export class Cache<T> {
|
|
|
13
13
|
|
|
14
14
|
protected async = new Map<string, Promise<T>>();
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Whether the data exists in the cache
|
|
18
|
+
*/
|
|
19
|
+
has(path: string): boolean {
|
|
20
|
+
return this.isEnabled && this.sync.has(path);
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
/**
|
|
17
24
|
* Gets data from the cache, if is exists and the cache is enabled.
|
|
18
25
|
*/
|
|
19
|
-
|
|
26
|
+
get(path: string): T | undefined {
|
|
20
27
|
if (!this.isEnabled) return;
|
|
21
28
|
|
|
22
29
|
return this.sync.get(path);
|
|
@@ -25,7 +32,7 @@ export class Cache<T> {
|
|
|
25
32
|
/**
|
|
26
33
|
* Adds data if the cache is enabled
|
|
27
34
|
*/
|
|
28
|
-
|
|
35
|
+
set(path: string, value: T): void {
|
|
29
36
|
if (!this.isEnabled) return;
|
|
30
37
|
|
|
31
38
|
this.sync.set(path, value);
|
|
@@ -33,18 +40,16 @@ export class Cache<T> {
|
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
/**
|
|
36
|
-
*
|
|
43
|
+
* Whether the data exists in the cache
|
|
37
44
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.sync.clear();
|
|
45
|
+
hasAsync(path: string): boolean {
|
|
46
|
+
return this.isEnabled && this.async.has(path);
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
50
|
* Gets data from the cache, if it exists and the cache is enabled.
|
|
46
51
|
*/
|
|
47
|
-
|
|
52
|
+
getAsync(path: string): Promise<T> | undefined {
|
|
48
53
|
if (!this.isEnabled) return;
|
|
49
54
|
|
|
50
55
|
return this.async.get(path);
|
|
@@ -53,7 +58,7 @@ export class Cache<T> {
|
|
|
53
58
|
/**
|
|
54
59
|
* Adds data if the cache is enabled
|
|
55
60
|
*/
|
|
56
|
-
|
|
61
|
+
setAsync(path: string, value: Promise<T>): void {
|
|
57
62
|
if (!this.isEnabled) return;
|
|
58
63
|
|
|
59
64
|
this.async.set(path, value);
|
|
@@ -65,7 +70,7 @@ export class Cache<T> {
|
|
|
65
70
|
*/
|
|
66
71
|
clear(): void {
|
|
67
72
|
if (!this.isEnabled) return;
|
|
68
|
-
|
|
73
|
+
this.sync.clear();
|
|
69
74
|
this.async.clear();
|
|
70
75
|
}
|
|
71
76
|
}
|
|
@@ -16,7 +16,7 @@ import * as cache from './cache.js';
|
|
|
16
16
|
import { config } from './config.js';
|
|
17
17
|
import * as constants from './constants.js';
|
|
18
18
|
import { Dir, Dirent } from './dir.js';
|
|
19
|
-
import { dirname, join, parse } from './path.js';
|
|
19
|
+
import { dirname, join, parse, resolve } from './path.js';
|
|
20
20
|
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
|
|
21
21
|
import { ReadStream, WriteStream } from './streams.js';
|
|
22
22
|
import { FSWatcher, emitChange } from './watchers.js';
|
|
@@ -395,6 +395,7 @@ export async function rename(oldPath: fs.PathLike, newPath: fs.PathLike): Promis
|
|
|
395
395
|
if (src.mountPoint == dst.mountPoint) {
|
|
396
396
|
await src.fs.rename(src.path, dst.path);
|
|
397
397
|
emitChange('rename', oldPath.toString());
|
|
398
|
+
emitChange('change', newPath.toString());
|
|
398
399
|
return;
|
|
399
400
|
}
|
|
400
401
|
await writeFile(newPath, await readFile(oldPath));
|
|
@@ -471,7 +472,7 @@ export async function unlink(path: fs.PathLike): Promise<void> {
|
|
|
471
472
|
path = normalizePath(path);
|
|
472
473
|
const { fs, path: resolved } = resolveMount(path);
|
|
473
474
|
try {
|
|
474
|
-
if (config.checkAccess && !(await (cache.stats.
|
|
475
|
+
if (config.checkAccess && !(await (cache.stats.getAsync(path) || fs.stat(resolved))).hasAccess(constants.W_OK)) {
|
|
475
476
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
476
477
|
}
|
|
477
478
|
await fs.unlink(resolved);
|
|
@@ -623,7 +624,7 @@ export async function rmdir(path: fs.PathLike): Promise<void> {
|
|
|
623
624
|
path = await realpath(path);
|
|
624
625
|
const { fs, path: resolved } = resolveMount(path);
|
|
625
626
|
try {
|
|
626
|
-
const stats = await (cache.stats.
|
|
627
|
+
const stats = await (cache.stats.getAsync(path) || fs.stat(resolved));
|
|
627
628
|
if (!stats) {
|
|
628
629
|
throw ErrnoError.With('ENOENT', path, 'rmdir');
|
|
629
630
|
}
|
|
@@ -718,8 +719,8 @@ export async function readdir(
|
|
|
718
719
|
|
|
719
720
|
const { fs, path: resolved } = resolveMount(path);
|
|
720
721
|
|
|
721
|
-
const _stats = cache.stats.
|
|
722
|
-
cache.stats.
|
|
722
|
+
const _stats = cache.stats.getAsync(path) || fs.stat(resolved).catch(handleError);
|
|
723
|
+
cache.stats.setAsync(path, _stats);
|
|
723
724
|
const stats = await _stats;
|
|
724
725
|
|
|
725
726
|
if (!stats) {
|
|
@@ -740,8 +741,8 @@ export async function readdir(
|
|
|
740
741
|
const addEntry = async (entry: string) => {
|
|
741
742
|
let entryStats: Stats | undefined;
|
|
742
743
|
if (options?.recursive || options?.withFileTypes) {
|
|
743
|
-
const _entryStats = cache.stats.
|
|
744
|
-
cache.stats.
|
|
744
|
+
const _entryStats = cache.stats.getAsync(join(path, entry)) || fs.stat(join(resolved, entry)).catch(handleError);
|
|
745
|
+
cache.stats.setAsync(join(path, entry), _entryStats);
|
|
745
746
|
entryStats = await _entryStats;
|
|
746
747
|
}
|
|
747
748
|
if (options?.withFileTypes) {
|
|
@@ -891,20 +892,25 @@ export async function realpath(path: fs.PathLike, options: fs.BufferEncodingOpti
|
|
|
891
892
|
export async function realpath(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding): Promise<string>;
|
|
892
893
|
export async function realpath(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding | fs.BufferEncodingOption): Promise<string | Buffer> {
|
|
893
894
|
path = normalizePath(path);
|
|
895
|
+
if (cache.paths.hasAsync(path)) return cache.paths.getAsync(path)!;
|
|
894
896
|
const { base, dir } = parse(path);
|
|
895
|
-
const
|
|
896
|
-
const
|
|
897
|
+
const realDir = dir == '/' ? '/' : await (cache.paths.getAsync(dir) || realpath(dir));
|
|
898
|
+
const lpath = join(realDir, base);
|
|
899
|
+
const { fs, path: resolvedPath } = resolveMount(lpath);
|
|
897
900
|
|
|
898
901
|
try {
|
|
899
|
-
const _stats = cache.stats.
|
|
900
|
-
cache.stats.
|
|
902
|
+
const _stats = cache.stats.getAsync(lpath) || fs.stat(resolvedPath);
|
|
903
|
+
cache.stats.setAsync(lpath, _stats);
|
|
901
904
|
if (!(await _stats).isSymbolicLink()) {
|
|
905
|
+
cache.paths.set(path, lpath);
|
|
902
906
|
return lpath;
|
|
903
907
|
}
|
|
904
908
|
|
|
905
|
-
const target =
|
|
909
|
+
const target = resolve(realDir, await readlink(lpath));
|
|
906
910
|
|
|
907
|
-
|
|
911
|
+
const real = cache.paths.getAsync(target) || realpath(target);
|
|
912
|
+
cache.paths.setAsync(path, real);
|
|
913
|
+
return await real;
|
|
908
914
|
} catch (e) {
|
|
909
915
|
if ((e as ErrnoError).code == 'ENOENT') {
|
|
910
916
|
return path;
|
|
@@ -968,7 +974,7 @@ access satisfies typeof promises.access;
|
|
|
968
974
|
export async function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOptions) {
|
|
969
975
|
path = normalizePath(path);
|
|
970
976
|
|
|
971
|
-
const stats = await (cache.stats.
|
|
977
|
+
const stats = await (cache.stats.getAsync(path) ||
|
|
972
978
|
stat(path).catch((error: ErrnoError) => {
|
|
973
979
|
if (error.code == 'ENOENT' && options?.force) return undefined;
|
|
974
980
|
throw error;
|
|
@@ -978,7 +984,7 @@ export async function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOpt
|
|
|
978
984
|
return;
|
|
979
985
|
}
|
|
980
986
|
|
|
981
|
-
cache.stats.
|
|
987
|
+
cache.stats.set(path, stats);
|
|
982
988
|
|
|
983
989
|
switch (stats.mode & constants.S_IFMT) {
|
|
984
990
|
case constants.S_IFDIR:
|
|
@@ -992,10 +998,10 @@ export async function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOpt
|
|
|
992
998
|
break;
|
|
993
999
|
case constants.S_IFREG:
|
|
994
1000
|
case constants.S_IFLNK:
|
|
995
|
-
await unlink(path);
|
|
996
|
-
break;
|
|
997
1001
|
case constants.S_IFBLK:
|
|
998
1002
|
case constants.S_IFCHR:
|
|
1003
|
+
await unlink(path);
|
|
1004
|
+
break;
|
|
999
1005
|
case constants.S_IFIFO:
|
|
1000
1006
|
case constants.S_IFSOCK:
|
|
1001
1007
|
default:
|
package/src/emulation/shared.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { FileSystem } from '../filesystem.js';
|
|
|
8
8
|
import { normalizePath } from '../utils.js';
|
|
9
9
|
import { resolve, type AbsolutePath } from './path.js';
|
|
10
10
|
import { size_max } from './constants.js';
|
|
11
|
+
import { paths as pathCache } from './cache.js';
|
|
11
12
|
|
|
12
13
|
// descriptors
|
|
13
14
|
export const fdMap: Map<number, File> = new Map();
|
|
@@ -47,6 +48,7 @@ export function mount(mountPoint: string, fs: FileSystem): void {
|
|
|
47
48
|
throw new ErrnoError(Errno.EINVAL, 'Mount point ' + mountPoint + ' is already in use.');
|
|
48
49
|
}
|
|
49
50
|
mounts.set(mountPoint, fs);
|
|
51
|
+
pathCache.clear();
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -61,6 +63,7 @@ export function umount(mountPoint: string): void {
|
|
|
61
63
|
throw new ErrnoError(Errno.EINVAL, 'Mount point ' + mountPoint + ' is already unmounted.');
|
|
62
64
|
}
|
|
63
65
|
mounts.delete(mountPoint);
|
|
66
|
+
pathCache.clear();
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/**
|
package/src/emulation/sync.ts
CHANGED
|
@@ -10,7 +10,7 @@ import * as cache from './cache.js';
|
|
|
10
10
|
import { config } from './config.js';
|
|
11
11
|
import * as constants from './constants.js';
|
|
12
12
|
import { Dir, Dirent } from './dir.js';
|
|
13
|
-
import { dirname, join, parse } from './path.js';
|
|
13
|
+
import { dirname, join, parse, resolve } from './path.js';
|
|
14
14
|
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
|
|
15
15
|
import { emitChange } from './watchers.js';
|
|
16
16
|
|
|
@@ -26,6 +26,7 @@ export function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void {
|
|
|
26
26
|
if (oldMount === newMount) {
|
|
27
27
|
oldMount.fs.renameSync(oldMount.path, newMount.path);
|
|
28
28
|
emitChange('rename', oldPath.toString());
|
|
29
|
+
emitChange('change', newPath.toString());
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -106,7 +107,7 @@ export function unlinkSync(path: fs.PathLike): void {
|
|
|
106
107
|
path = normalizePath(path);
|
|
107
108
|
const { fs, path: resolved } = resolveMount(path);
|
|
108
109
|
try {
|
|
109
|
-
if (config.checkAccess && !(cache.stats.
|
|
110
|
+
if (config.checkAccess && !(cache.stats.get(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
|
|
110
111
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
111
112
|
}
|
|
112
113
|
fs.unlinkSync(resolved);
|
|
@@ -386,7 +387,7 @@ export function rmdirSync(path: fs.PathLike): void {
|
|
|
386
387
|
path = normalizePath(path);
|
|
387
388
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
388
389
|
try {
|
|
389
|
-
const stats = cache.stats.
|
|
390
|
+
const stats = cache.stats.get(path) || fs.statSync(resolved);
|
|
390
391
|
if (!stats.isDirectory()) {
|
|
391
392
|
throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
|
|
392
393
|
}
|
|
@@ -459,8 +460,8 @@ export function readdirSync(
|
|
|
459
460
|
const { fs, path: resolved } = resolveMount(realpathSync(path));
|
|
460
461
|
let entries: string[];
|
|
461
462
|
try {
|
|
462
|
-
const stats = cache.stats.
|
|
463
|
-
cache.stats.
|
|
463
|
+
const stats = cache.stats.get(path) || fs.statSync(resolved);
|
|
464
|
+
cache.stats.set(path, stats);
|
|
464
465
|
if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
|
|
465
466
|
throw ErrnoError.With('EACCES', resolved, 'readdir');
|
|
466
467
|
}
|
|
@@ -475,8 +476,8 @@ export function readdirSync(
|
|
|
475
476
|
// Iterate over entries and handle recursive case if needed
|
|
476
477
|
const values: (string | Dirent | Buffer)[] = [];
|
|
477
478
|
for (const entry of entries) {
|
|
478
|
-
const entryStat = cache.stats.
|
|
479
|
-
cache.stats.
|
|
479
|
+
const entryStat = cache.stats.get(join(path, entry)) || fs.statSync(join(resolved, entry));
|
|
480
|
+
cache.stats.set(join(path, entry), entryStat);
|
|
480
481
|
|
|
481
482
|
if (options?.withFileTypes) {
|
|
482
483
|
values.push(new Dirent(entry, entryStat));
|
|
@@ -500,7 +501,7 @@ export function readdirSync(
|
|
|
500
501
|
}
|
|
501
502
|
|
|
502
503
|
if (!options?._isIndirect) {
|
|
503
|
-
cache.stats.
|
|
504
|
+
cache.stats.clear();
|
|
504
505
|
}
|
|
505
506
|
return values as string[] | Dirent[] | Buffer[];
|
|
506
507
|
}
|
|
@@ -621,18 +622,24 @@ export function realpathSync(path: fs.PathLike, options: fs.BufferEncodingOption
|
|
|
621
622
|
export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption): string;
|
|
622
623
|
export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs.BufferEncodingOption): string | Buffer {
|
|
623
624
|
path = normalizePath(path);
|
|
625
|
+
if (cache.paths.has(path)) return cache.paths.get(path)!;
|
|
624
626
|
const { base, dir } = parse(path);
|
|
625
|
-
const
|
|
626
|
-
const
|
|
627
|
+
const realDir = dir == '/' ? '/' : cache.paths.get(dir) || realpathSync(dir);
|
|
628
|
+
const lpath = join(realDir, base);
|
|
629
|
+
const { fs, path: resolvedPath } = resolveMount(lpath);
|
|
627
630
|
|
|
628
631
|
try {
|
|
629
|
-
const stats = fs.statSync(resolvedPath);
|
|
632
|
+
const stats = cache.stats.get(lpath) || fs.statSync(resolvedPath);
|
|
633
|
+
cache.stats.set(lpath, stats);
|
|
630
634
|
if (!stats.isSymbolicLink()) {
|
|
635
|
+
cache.paths.set(path, lpath);
|
|
631
636
|
return lpath;
|
|
632
637
|
}
|
|
633
638
|
|
|
634
|
-
const target =
|
|
635
|
-
|
|
639
|
+
const target = resolve(realDir, readlinkSync(lpath, options).toString());
|
|
640
|
+
const real = cache.paths.get(target) || realpathSync(target);
|
|
641
|
+
cache.paths.set(path, real);
|
|
642
|
+
return real;
|
|
636
643
|
} catch (e) {
|
|
637
644
|
if ((e as ErrnoError).code == 'ENOENT') {
|
|
638
645
|
return path;
|
|
@@ -659,7 +666,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
|
|
|
659
666
|
|
|
660
667
|
let stats: Stats | undefined;
|
|
661
668
|
try {
|
|
662
|
-
stats = cache.stats.
|
|
669
|
+
stats = cache.stats.get(path) || statSync(path);
|
|
663
670
|
} catch (error) {
|
|
664
671
|
if ((error as ErrnoError).code != 'ENOENT' || !options?.force) throw error;
|
|
665
672
|
}
|
|
@@ -668,7 +675,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
|
|
|
668
675
|
return;
|
|
669
676
|
}
|
|
670
677
|
|
|
671
|
-
cache.stats.
|
|
678
|
+
cache.stats.set(path, stats);
|
|
672
679
|
|
|
673
680
|
switch (stats.mode & constants.S_IFMT) {
|
|
674
681
|
case constants.S_IFDIR:
|
|
@@ -682,19 +689,19 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
|
|
|
682
689
|
break;
|
|
683
690
|
case constants.S_IFREG:
|
|
684
691
|
case constants.S_IFLNK:
|
|
685
|
-
unlinkSync(path);
|
|
686
|
-
break;
|
|
687
692
|
case constants.S_IFBLK:
|
|
688
693
|
case constants.S_IFCHR:
|
|
694
|
+
unlinkSync(path);
|
|
695
|
+
break;
|
|
689
696
|
case constants.S_IFIFO:
|
|
690
697
|
case constants.S_IFSOCK:
|
|
691
698
|
default:
|
|
692
|
-
cache.stats.
|
|
699
|
+
cache.stats.clear();
|
|
693
700
|
throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
|
|
694
701
|
}
|
|
695
702
|
|
|
696
703
|
if (!options?._isIndirect) {
|
|
697
|
-
cache.stats.
|
|
704
|
+
cache.stats.clear();
|
|
698
705
|
}
|
|
699
706
|
}
|
|
700
707
|
rmSync satisfies typeof fs.rmSync;
|
package/src/stats.ts
CHANGED
|
@@ -247,10 +247,7 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
247
247
|
* @internal
|
|
248
248
|
*/
|
|
249
249
|
public hasAccess(mode: number): boolean {
|
|
250
|
-
if (credentials.euid === 0 || credentials.egid === 0)
|
|
251
|
-
// Running as root
|
|
252
|
-
return true;
|
|
253
|
-
}
|
|
250
|
+
if (this.isSymbolicLink() || credentials.euid === 0 || credentials.egid === 0) return true;
|
|
254
251
|
|
|
255
252
|
let perm = 0;
|
|
256
253
|
|
package/tests/fs/links.test.ts
CHANGED
|
@@ -23,6 +23,10 @@ suite('Links', () => {
|
|
|
23
23
|
assert.strictEqual(destination, target);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
test('read target contents', async () => {
|
|
27
|
+
assert.equal(await fs.promises.readFile(target, 'utf-8'), await fs.promises.readFile(symlink, 'utf-8'));
|
|
28
|
+
});
|
|
29
|
+
|
|
26
30
|
test('unlink', async () => {
|
|
27
31
|
await fs.promises.unlink(symlink);
|
|
28
32
|
assert(!(await fs.promises.exists(symlink)));
|
package/tests/fs/watch.test.ts
CHANGED
|
@@ -71,18 +71,29 @@ suite('Watch Features', () => {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
test('fs.watch should detect file renames', async () => {
|
|
74
|
-
const
|
|
75
|
-
const
|
|
74
|
+
const oldFileName = `oldFile.txt`;
|
|
75
|
+
const newFileName = `newFile.txt`;
|
|
76
|
+
const oldFile = `${testDir}/${oldFileName}`;
|
|
77
|
+
const newFile = `${testDir}/${newFileName}`;
|
|
76
78
|
|
|
77
79
|
await fs.promises.writeFile(oldFile, 'Some content');
|
|
80
|
+
const oldFileResolver = Promise.withResolvers<void>();
|
|
81
|
+
const newFileResolver = Promise.withResolvers<void>();
|
|
78
82
|
|
|
83
|
+
const fileResolvers: Record<string, { resolver: PromiseWithResolvers<void>; eventType: string }> = {
|
|
84
|
+
[oldFileName]: { resolver: oldFileResolver, eventType: 'rename' },
|
|
85
|
+
[newFileName]: { resolver: newFileResolver, eventType: 'change' },
|
|
86
|
+
};
|
|
79
87
|
using watcher = fs.watch(testDir, (eventType, filename) => {
|
|
80
|
-
|
|
81
|
-
assert.
|
|
88
|
+
const resolver = fileResolvers[filename];
|
|
89
|
+
assert.notEqual(resolver, undefined); // should have a resolver so file is expected
|
|
90
|
+
assert.strictEqual(eventType, resolver.eventType);
|
|
91
|
+
resolver.resolver.resolve();
|
|
82
92
|
});
|
|
83
93
|
|
|
84
94
|
// Rename the file to trigger the event
|
|
85
95
|
await fs.promises.rename(oldFile, newFile);
|
|
96
|
+
await Promise.all([newFileResolver.promise, oldFileResolver.promise]);
|
|
86
97
|
});
|
|
87
98
|
|
|
88
99
|
test('fs.watch should detect file deletions', async () => {
|