@zenfs/core 1.3.3 → 1.3.5
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/port/fs.d.ts +1 -2
- package/dist/backends/port/fs.js +0 -3
- package/dist/config.d.ts +5 -3
- package/dist/config.js +4 -6
- package/dist/credentials.d.ts +14 -2
- package/dist/credentials.js +11 -9
- package/dist/emulation/cache.d.ts +10 -6
- package/dist/emulation/cache.js +14 -9
- package/dist/emulation/promises.js +25 -18
- package/dist/emulation/shared.js +3 -0
- package/dist/emulation/sync.js +28 -20
- package/dist/emulation/watchers.js +9 -9
- package/dist/file.d.ts +1 -13
- package/dist/file.js +7 -23
- package/dist/stats.d.ts +11 -7
- package/dist/stats.js +14 -15
- package/package.json +4 -2
- package/readme.md +2 -0
- package/scripts/make-index.js +24 -7
- package/src/backends/port/fs.ts +1 -5
- package/src/config.ts +9 -9
- package/src/credentials.ts +24 -9
- package/src/emulation/cache.ts +15 -10
- package/src/emulation/promises.ts +24 -18
- package/src/emulation/shared.ts +3 -0
- package/src/emulation/sync.ts +27 -20
- package/src/emulation/watchers.ts +10 -9
- package/src/file.ts +7 -37
- package/src/stats.ts +21 -18
- package/tests/fs/links.test.ts +4 -0
- package/tests/fs/watch.test.ts +36 -4
package/dist/file.js
CHANGED
|
@@ -2,7 +2,7 @@ import { config } from './emulation/config.js';
|
|
|
2
2
|
import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT, size_max } from './emulation/constants.js';
|
|
3
3
|
import { Errno, ErrnoError } from './error.js';
|
|
4
4
|
import './polyfills.js';
|
|
5
|
-
import { Stats } from './stats.js';
|
|
5
|
+
import { _chown, Stats } from './stats.js';
|
|
6
6
|
const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
|
|
7
7
|
export function parseFlag(flag) {
|
|
8
8
|
if (typeof flag === 'number') {
|
|
@@ -411,8 +411,8 @@ export class PreloadFile extends File {
|
|
|
411
411
|
throw ErrnoError.With('EBADF', this.path, 'File.chmod');
|
|
412
412
|
}
|
|
413
413
|
this.dirty = true;
|
|
414
|
-
this.stats.
|
|
415
|
-
if (config.syncImmediately)
|
|
414
|
+
this.stats.mode = (this.stats.mode & (mode > S_IFMT ? ~S_IFMT : S_IFMT)) | mode;
|
|
415
|
+
if (config.syncImmediately || mode > S_IFMT)
|
|
416
416
|
await this.sync();
|
|
417
417
|
}
|
|
418
418
|
chmodSync(mode) {
|
|
@@ -420,8 +420,8 @@ export class PreloadFile extends File {
|
|
|
420
420
|
throw ErrnoError.With('EBADF', this.path, 'File.chmod');
|
|
421
421
|
}
|
|
422
422
|
this.dirty = true;
|
|
423
|
-
this.stats.
|
|
424
|
-
if (config.syncImmediately)
|
|
423
|
+
this.stats.mode = (this.stats.mode & (mode > S_IFMT ? ~S_IFMT : S_IFMT)) | mode;
|
|
424
|
+
if (config.syncImmediately || mode > S_IFMT)
|
|
425
425
|
this.syncSync();
|
|
426
426
|
}
|
|
427
427
|
async chown(uid, gid) {
|
|
@@ -429,7 +429,7 @@ export class PreloadFile extends File {
|
|
|
429
429
|
throw ErrnoError.With('EBADF', this.path, 'File.chown');
|
|
430
430
|
}
|
|
431
431
|
this.dirty = true;
|
|
432
|
-
this.stats
|
|
432
|
+
_chown(this.stats, uid, gid);
|
|
433
433
|
if (config.syncImmediately)
|
|
434
434
|
await this.sync();
|
|
435
435
|
}
|
|
@@ -438,7 +438,7 @@ export class PreloadFile extends File {
|
|
|
438
438
|
throw ErrnoError.With('EBADF', this.path, 'File.chown');
|
|
439
439
|
}
|
|
440
440
|
this.dirty = true;
|
|
441
|
-
this.stats
|
|
441
|
+
_chown(this.stats, uid, gid);
|
|
442
442
|
if (config.syncImmediately)
|
|
443
443
|
this.syncSync();
|
|
444
444
|
}
|
|
@@ -462,22 +462,6 @@ export class PreloadFile extends File {
|
|
|
462
462
|
if (config.syncImmediately)
|
|
463
463
|
this.syncSync();
|
|
464
464
|
}
|
|
465
|
-
async _setType(type) {
|
|
466
|
-
if (this.closed) {
|
|
467
|
-
throw ErrnoError.With('EBADF', this.path, 'File._setType');
|
|
468
|
-
}
|
|
469
|
-
this.dirty = true;
|
|
470
|
-
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
471
|
-
await this.sync();
|
|
472
|
-
}
|
|
473
|
-
_setTypeSync(type) {
|
|
474
|
-
if (this.closed) {
|
|
475
|
-
throw ErrnoError.With('EBADF', this.path, 'File._setType');
|
|
476
|
-
}
|
|
477
|
-
this.dirty = true;
|
|
478
|
-
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
479
|
-
this.syncSync();
|
|
480
|
-
}
|
|
481
465
|
}
|
|
482
466
|
/**
|
|
483
467
|
* For the file systems which do not sync to anything.
|
package/dist/stats.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type * as Node from 'node:fs';
|
|
2
|
-
import { type Credentials } from './credentials.js';
|
|
3
2
|
import { S_IFDIR, S_IFLNK, S_IFREG } from './emulation/constants.js';
|
|
4
3
|
/**
|
|
5
4
|
* Indicates the type of a file. Applied to 'mode'.
|
|
@@ -43,6 +42,10 @@ export interface StatsLike<T extends number | bigint = number | bigint> {
|
|
|
43
42
|
* Inode number
|
|
44
43
|
*/
|
|
45
44
|
ino: T;
|
|
45
|
+
/**
|
|
46
|
+
* Number of hard links
|
|
47
|
+
*/
|
|
48
|
+
nlink: T;
|
|
46
49
|
}
|
|
47
50
|
/**
|
|
48
51
|
* Provides information about a particular entry in the file system.
|
|
@@ -136,28 +139,29 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
|
|
|
136
139
|
* @internal
|
|
137
140
|
*/
|
|
138
141
|
hasAccess(mode: number): boolean;
|
|
139
|
-
/**
|
|
140
|
-
* Convert the current stats object into a credentials object
|
|
141
|
-
* @internal
|
|
142
|
-
*/
|
|
143
|
-
cred(uid?: number, gid?: number): Credentials;
|
|
144
142
|
/**
|
|
145
143
|
* Change the mode of the file.
|
|
146
144
|
* We use this helper function to prevent messing up the type of the file.
|
|
147
145
|
* @internal
|
|
146
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
148
147
|
*/
|
|
149
148
|
chmod(mode: number): void;
|
|
150
149
|
/**
|
|
151
150
|
* Change the owner user/group of the file.
|
|
152
151
|
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
153
152
|
* @internal
|
|
153
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
154
154
|
*/
|
|
155
|
-
chown(uid: number
|
|
155
|
+
chown(uid: number, gid: number): void;
|
|
156
156
|
get atimeNs(): bigint;
|
|
157
157
|
get mtimeNs(): bigint;
|
|
158
158
|
get ctimeNs(): bigint;
|
|
159
159
|
get birthtimeNs(): bigint;
|
|
160
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* @hidden @internal
|
|
163
|
+
*/
|
|
164
|
+
export declare function _chown(stats: Partial<StatsLike<number>>, uid: number, gid: number): void;
|
|
161
165
|
/**
|
|
162
166
|
* Implementation of Node's `Stats`.
|
|
163
167
|
*
|
package/dist/stats.js
CHANGED
|
@@ -124,7 +124,7 @@ export class StatsCommon {
|
|
|
124
124
|
perm |= X_OK;
|
|
125
125
|
}
|
|
126
126
|
// Group permissions
|
|
127
|
-
if (credentials.gid === this.gid) {
|
|
127
|
+
if (credentials.gid === this.gid || credentials.groups.includes(Number(this.gid))) {
|
|
128
128
|
if (this.mode & S_IRGRP)
|
|
129
129
|
perm |= R_OK;
|
|
130
130
|
if (this.mode & S_IWGRP)
|
|
@@ -142,24 +142,11 @@ export class StatsCommon {
|
|
|
142
142
|
// Perform the access check
|
|
143
143
|
return (perm & mode) === mode;
|
|
144
144
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Convert the current stats object into a credentials object
|
|
147
|
-
* @internal
|
|
148
|
-
*/
|
|
149
|
-
cred(uid = Number(this.uid), gid = Number(this.gid)) {
|
|
150
|
-
return {
|
|
151
|
-
uid,
|
|
152
|
-
gid,
|
|
153
|
-
suid: Number(this.uid),
|
|
154
|
-
sgid: Number(this.gid),
|
|
155
|
-
euid: uid,
|
|
156
|
-
egid: gid,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
145
|
/**
|
|
160
146
|
* Change the mode of the file.
|
|
161
147
|
* We use this helper function to prevent messing up the type of the file.
|
|
162
148
|
* @internal
|
|
149
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
163
150
|
*/
|
|
164
151
|
chmod(mode) {
|
|
165
152
|
this.mode = this._convert((this.mode & S_IFMT) | mode);
|
|
@@ -168,6 +155,7 @@ export class StatsCommon {
|
|
|
168
155
|
* Change the owner user/group of the file.
|
|
169
156
|
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
170
157
|
* @internal
|
|
158
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
171
159
|
*/
|
|
172
160
|
chown(uid, gid) {
|
|
173
161
|
uid = Number(uid);
|
|
@@ -192,6 +180,17 @@ export class StatsCommon {
|
|
|
192
180
|
return BigInt(this.birthtimeMs) * 1000n;
|
|
193
181
|
}
|
|
194
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* @hidden @internal
|
|
185
|
+
*/
|
|
186
|
+
export function _chown(stats, uid, gid) {
|
|
187
|
+
if (!isNaN(uid) && 0 <= uid && uid < 2 ** 32) {
|
|
188
|
+
stats.uid = uid;
|
|
189
|
+
}
|
|
190
|
+
if (!isNaN(gid) && 0 <= gid && gid < 2 ** 32) {
|
|
191
|
+
stats.gid = gid;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
195
194
|
/**
|
|
196
195
|
* Implementation of Node's `Stats`.
|
|
197
196
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
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/backends/port/fs.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Errno, ErrnoError } from '../../error.js';
|
|
|
6
6
|
import { File } from '../../file.js';
|
|
7
7
|
import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
|
|
8
8
|
import { Async } from '../../mixins/async.js';
|
|
9
|
-
import { Stats
|
|
9
|
+
import { Stats } from '../../stats.js';
|
|
10
10
|
import type { Backend, FilesystemOf } from '../backend.js';
|
|
11
11
|
import { InMemory } from '../memory.js';
|
|
12
12
|
import * as RPC from './rpc.js';
|
|
@@ -104,10 +104,6 @@ export class PortFile extends File {
|
|
|
104
104
|
this._throwNoSync('utimes');
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
public _setType(type: FileType): Promise<void> {
|
|
108
|
-
return this.rpc('_setType', type);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
107
|
public _setTypeSync(): void {
|
|
112
108
|
this._throwNoSync('_setType');
|
|
113
109
|
}
|
package/src/config.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js';
|
|
2
2
|
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
|
|
3
|
-
import {
|
|
3
|
+
import { useCredentials } from './credentials.js';
|
|
4
4
|
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
|
|
@@ -188,7 +190,7 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
|
|
|
188
190
|
const uid = 'uid' in configuration ? configuration.uid || 0 : 0;
|
|
189
191
|
const gid = 'gid' in configuration ? configuration.gid || 0 : 0;
|
|
190
192
|
|
|
191
|
-
|
|
193
|
+
useCredentials({ uid, gid });
|
|
192
194
|
|
|
193
195
|
cache.stats.isEnabled = configuration.cacheStats ?? false;
|
|
194
196
|
cache.paths.isEnabled = configuration.cachePaths ?? false;
|
|
@@ -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/credentials.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface Credentials {
|
|
|
10
10
|
sgid: number;
|
|
11
11
|
euid: number;
|
|
12
12
|
egid: number;
|
|
13
|
+
/**
|
|
14
|
+
* List of group IDs.
|
|
15
|
+
*/
|
|
16
|
+
groups: number[];
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export const credentials: Credentials = {
|
|
@@ -19,16 +23,27 @@ export const credentials: Credentials = {
|
|
|
19
23
|
sgid: 0,
|
|
20
24
|
euid: 0,
|
|
21
25
|
egid: 0,
|
|
26
|
+
groups: [],
|
|
22
27
|
};
|
|
23
28
|
|
|
29
|
+
export interface CredentialInit {
|
|
30
|
+
uid: number;
|
|
31
|
+
gid: number;
|
|
32
|
+
suid?: number;
|
|
33
|
+
sgid?: number;
|
|
34
|
+
euid?: number;
|
|
35
|
+
egid?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
/**
|
|
25
|
-
*
|
|
39
|
+
* Uses credentials from the provided uid and gid.
|
|
26
40
|
*/
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
41
|
+
export function useCredentials(source: CredentialInit): void {
|
|
42
|
+
Object.assign(credentials, {
|
|
43
|
+
suid: source.uid,
|
|
44
|
+
sgid: source.gid,
|
|
45
|
+
euid: source.uid,
|
|
46
|
+
egid: source.gid,
|
|
47
|
+
...source,
|
|
48
|
+
});
|
|
49
|
+
}
|
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) {
|
|
@@ -822,7 +823,7 @@ export async function symlink(target: fs.PathLike, path: fs.PathLike, type: fs.s
|
|
|
822
823
|
|
|
823
824
|
await using handle = await _open(path, 'w+', 0o644, false);
|
|
824
825
|
await handle.writeFile(target.toString());
|
|
825
|
-
await handle.file.
|
|
826
|
+
await handle.file.chmod(constants.S_IFLNK);
|
|
826
827
|
}
|
|
827
828
|
symlink satisfies typeof promises.symlink;
|
|
828
829
|
|
|
@@ -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
|
/**
|