@zenfs/core 2.7.6 → 2.7.7
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/store/fs.js +1 -3
- package/dist/backends/store/store.js +1 -3
- package/dist/config.d.ts +7 -0
- package/dist/config.js +9 -3
- package/dist/internal/error.d.ts +5 -0
- package/dist/internal/error.js +10 -0
- package/dist/internal/inode.d.ts +25 -0
- package/dist/internal/inode.js +75 -10
- package/dist/mixins/async.js +22 -24
- package/dist/node/promises.js +17 -21
- package/dist/node/stats.d.ts +1 -1
- package/dist/node/stats.js +4 -1
- package/dist/node/sync.js +14 -18
- package/dist/polyfills.js +16 -0
- package/dist/vfs/async.js +77 -64
- package/dist/vfs/config.d.ts +10 -1
- package/dist/vfs/config.js +5 -2
- package/dist/vfs/file.js +10 -4
- package/dist/vfs/ioctl.js +2 -2
- package/dist/vfs/shared.d.ts +2 -0
- package/dist/vfs/shared.js +3 -4
- package/dist/vfs/sync.js +77 -60
- package/dist/vfs/vcache.d.ts +14 -14
- package/dist/vfs/vcache.js +65 -57
- package/dist/vfs/watchers.d.ts +1 -0
- package/dist/vfs/watchers.js +2 -4
- package/dist/vfs/xattr.js +18 -17
- package/package.json +2 -3
- package/scripts/make-index.js +1 -1
- package/tests/common/async.test.ts +35 -0
- package/tests/common/config.test.ts +1 -1
- package/tests/common/file_index.test.ts +1 -1
- package/tests/common/mounts.test.ts +12 -0
- package/tests/common/vcache.test.ts +13 -0
- package/tests/fs/concurrency.test.ts +3 -3
- package/tests/fs/links.test.ts +76 -0
- package/tests/fs/permissions.test.ts +232 -0
- package/tests/fs/stat.test.ts +1 -1
- package/tests/fs/streams.test.ts +1 -1
- package/tests/fs/xattr.test.ts +10 -0
|
@@ -93,9 +93,7 @@ export class StoreFS extends FileSystem {
|
|
|
93
93
|
* Add a inode/path pair
|
|
94
94
|
*/
|
|
95
95
|
_add(ino, path) {
|
|
96
|
-
|
|
97
|
-
this._paths.set(ino, new Set());
|
|
98
|
-
this._paths.get(ino).add(path);
|
|
96
|
+
this._paths.getOrInsertComputed(ino, () => new Set()).add(path);
|
|
99
97
|
this._ids.set(path, ino);
|
|
100
98
|
}
|
|
101
99
|
/**
|
|
@@ -202,9 +202,7 @@ export class WrappedTransaction {
|
|
|
202
202
|
* on during the transaction.
|
|
203
203
|
*/
|
|
204
204
|
stash(id, data, offset = 0) {
|
|
205
|
-
|
|
206
|
-
this.originalData.set(id, []);
|
|
207
|
-
this.originalData.get(id).push({ data, offset });
|
|
205
|
+
this.originalData.getOrInsertComputed(id, () => []).push({ data, offset });
|
|
208
206
|
}
|
|
209
207
|
/**
|
|
210
208
|
* Marks an id as modified, and stashes its value if it has not been stashed already.
|
package/dist/config.d.ts
CHANGED
|
@@ -72,6 +72,12 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
|
|
|
72
72
|
* @default false
|
|
73
73
|
*/
|
|
74
74
|
disableAccessChecks: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* If true, path resolution always walks each directory in a path, checking search permission on each.
|
|
77
|
+
* This is more correct but can be much slower.
|
|
78
|
+
* @default false
|
|
79
|
+
*/
|
|
80
|
+
resolveFullWalk: boolean;
|
|
75
81
|
/**
|
|
76
82
|
* If true, files will only sync to the file system when closed.
|
|
77
83
|
* This overrides `disableUpdateOnRead`
|
|
@@ -79,6 +85,7 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
|
|
|
79
85
|
* This can increase performance.
|
|
80
86
|
* @experimental
|
|
81
87
|
* @default false
|
|
88
|
+
* @deprecated Replaced by the `Sync` inode flag in v2.0.0
|
|
82
89
|
*/
|
|
83
90
|
onlySyncOnClose: boolean;
|
|
84
91
|
/**
|
package/dist/config.js
CHANGED
|
@@ -6,7 +6,7 @@ import { DeviceFS } from './internal/devices.js';
|
|
|
6
6
|
import { FileSystem } from './internal/filesystem.js';
|
|
7
7
|
import { exists, mkdir, stat } from './node/promises.js';
|
|
8
8
|
import { existsSync, mkdirSync, statSync } from './node/sync.js';
|
|
9
|
-
import {
|
|
9
|
+
import { _setVFSConfig } from './vfs/config.js';
|
|
10
10
|
import { mount, umount } from './vfs/shared.js';
|
|
11
11
|
/**
|
|
12
12
|
* Update the configuration of a file system.
|
|
@@ -209,7 +209,10 @@ export async function configure(configuration) {
|
|
|
209
209
|
uid: configuration.uid || 0,
|
|
210
210
|
gid: configuration.gid || 0,
|
|
211
211
|
}));
|
|
212
|
-
|
|
212
|
+
_setVFSConfig({
|
|
213
|
+
checkAccess: !configuration.disableAccessChecks,
|
|
214
|
+
resolveFullWalk: !!configuration.resolveFullWalk,
|
|
215
|
+
});
|
|
213
216
|
if (configuration.log)
|
|
214
217
|
log.configure(configuration.log);
|
|
215
218
|
if (configuration.mounts) {
|
|
@@ -257,7 +260,10 @@ export function configureSync(configuration) {
|
|
|
257
260
|
uid: configuration.uid || 0,
|
|
258
261
|
gid: configuration.gid || 0,
|
|
259
262
|
}));
|
|
260
|
-
|
|
263
|
+
_setVFSConfig({
|
|
264
|
+
checkAccess: !configuration.disableAccessChecks,
|
|
265
|
+
resolveFullWalk: !!configuration.resolveFullWalk,
|
|
266
|
+
});
|
|
261
267
|
if (configuration.log)
|
|
262
268
|
log.configure(configuration.log);
|
|
263
269
|
if (configuration.mounts) {
|
package/dist/internal/error.d.ts
CHANGED
|
@@ -25,3 +25,8 @@ export declare function wrap<const FS, const Prop extends keyof FS & string>(fs:
|
|
|
25
25
|
* Wraps an `fs` so that thrown errors aren't empty
|
|
26
26
|
*/
|
|
27
27
|
export declare function withExceptionContext<const FS extends FileSystem>(fs: FS, context: ExceptionExtra): FS;
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
* Returns the file system wrapped by {@link withExceptionContext}, or `fs` if it is not wrapped
|
|
31
|
+
*/
|
|
32
|
+
export declare function withoutExceptionContext<const FS extends FileSystem>(fs: FS): FS;
|
package/dist/internal/error.js
CHANGED
|
@@ -26,6 +26,7 @@ export function wrap(fs, prop, path, dest) {
|
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
const kTarget = Symbol('withExceptionContext:target');
|
|
29
30
|
/**
|
|
30
31
|
* @internal
|
|
31
32
|
* Wraps an `fs` so that thrown errors aren't empty
|
|
@@ -33,6 +34,8 @@ export function wrap(fs, prop, path, dest) {
|
|
|
33
34
|
export function withExceptionContext(fs, context) {
|
|
34
35
|
return new Proxy(fs, {
|
|
35
36
|
get(target, prop) {
|
|
37
|
+
if (prop === kTarget)
|
|
38
|
+
return target;
|
|
36
39
|
const value = Reflect.get(target, prop);
|
|
37
40
|
if (typeof value != 'function')
|
|
38
41
|
return value;
|
|
@@ -58,3 +61,10 @@ export function withExceptionContext(fs, context) {
|
|
|
58
61
|
},
|
|
59
62
|
});
|
|
60
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* @internal
|
|
66
|
+
* Returns the file system wrapped by {@link withExceptionContext}, or `fs` if it is not wrapped
|
|
67
|
+
*/
|
|
68
|
+
export function withoutExceptionContext(fs) {
|
|
69
|
+
return fs[kTarget] ?? fs;
|
|
70
|
+
}
|
package/dist/internal/inode.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ExceptionExtra } from 'kerium';
|
|
1
2
|
import { BufferView } from 'utilium/buffer';
|
|
2
3
|
import { Stats } from '../node/stats.js';
|
|
3
4
|
import { type V_Context } from './contexts.js';
|
|
@@ -234,10 +235,34 @@ export declare function isFIFO(metadata: {
|
|
|
234
235
|
* @internal
|
|
235
236
|
*/
|
|
236
237
|
export declare function hasAccess($: V_Context, inode: Pick<InodeLike, 'mode' | 'uid' | 'gid'>, access: number): boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Throws if a given user may not search the directory `dir`
|
|
240
|
+
* @internal
|
|
241
|
+
*/
|
|
242
|
+
export declare function assertSearchable($: V_Context, dir: Pick<InodeLike, 'mode' | 'uid' | 'gid'>, extra?: ExceptionExtra): void;
|
|
243
|
+
/**
|
|
244
|
+
* Throws if a given user may not remove `entry` from the directory `parent`
|
|
245
|
+
* @internal
|
|
246
|
+
*/
|
|
247
|
+
export declare function assertRemovable($: V_Context, parent: Pick<InodeLike, 'mode' | 'uid' | 'gid'>, entry: Pick<InodeLike, 'uid'>, extra?: ExceptionExtra): void;
|
|
248
|
+
/**
|
|
249
|
+
* Throws if a given user is neither root nor the owner of `inode`
|
|
250
|
+
* @internal
|
|
251
|
+
*/
|
|
252
|
+
export declare function assertOwner($: V_Context, inode: Pick<InodeLike, 'uid'>, extra?: ExceptionExtra): void;
|
|
253
|
+
/**
|
|
254
|
+
* Throws if a given user may not change the owner of `inode` to `uid` and `gid`, where `-1` leaves an ID unchanged
|
|
255
|
+
* @internal
|
|
256
|
+
*/
|
|
257
|
+
export declare function assertCanChown($: V_Context, inode: Pick<InodeLike, 'uid' | 'gid'>, uid: number, gid: number, extra?: ExceptionExtra): void;
|
|
237
258
|
/**
|
|
238
259
|
* @hidden @internal
|
|
239
260
|
*
|
|
240
261
|
* @todo https://github.com/nodejs/node/pull/58836 support -1 for `*chown`
|
|
241
262
|
*/
|
|
242
263
|
export declare function _chown(stats: Partial<InodeLike>, uid: number, gid: number): boolean;
|
|
264
|
+
/**
|
|
265
|
+
* @hidden @internal
|
|
266
|
+
*/
|
|
267
|
+
export declare function _chmod($: V_Context, inode: Pick<InodeLike, 'mode' | 'gid'>, mode: number): void;
|
|
243
268
|
export {};
|
package/dist/internal/inode.js
CHANGED
|
@@ -33,7 +33,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
33
33
|
return useValue ? value : void 0;
|
|
34
34
|
};
|
|
35
35
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
36
|
-
import { withErrno } from 'kerium';
|
|
36
|
+
import { UV, withErrno } from 'kerium';
|
|
37
37
|
import { crit, warn } from 'kerium/log';
|
|
38
38
|
import { sizeof } from 'memium';
|
|
39
39
|
import { $from, field, struct, types as t } from 'memium/decorators';
|
|
@@ -41,6 +41,7 @@ import { decodeUTF8, encodeUTF8, pick } from 'utilium';
|
|
|
41
41
|
import { BufferView } from 'utilium/buffer';
|
|
42
42
|
import * as c from '../constants.js';
|
|
43
43
|
import { Stats } from '../node/stats.js';
|
|
44
|
+
import { checkAccess } from '../vfs/config.js';
|
|
44
45
|
import { contextOf } from './contexts.js';
|
|
45
46
|
/**
|
|
46
47
|
* Root inode
|
|
@@ -654,10 +655,10 @@ export function hasAccess($, inode, access) {
|
|
|
654
655
|
const { credentials } = contextOf($);
|
|
655
656
|
if (isSymbolicLink(inode))
|
|
656
657
|
return true;
|
|
657
|
-
if (credentials.euid === 0
|
|
658
|
+
if (credentials.euid === 0)
|
|
658
659
|
return !(access & c.X_OK) || isDirectory(inode) || !!(inode.mode & (c.S_IXUSR | c.S_IXGRP | c.S_IXOTH));
|
|
659
660
|
let perm = 0;
|
|
660
|
-
if (credentials.
|
|
661
|
+
if (credentials.euid === inode.uid) {
|
|
661
662
|
if (inode.mode & c.S_IRUSR)
|
|
662
663
|
perm |= c.R_OK;
|
|
663
664
|
if (inode.mode & c.S_IWUSR)
|
|
@@ -665,7 +666,7 @@ export function hasAccess($, inode, access) {
|
|
|
665
666
|
if (inode.mode & c.S_IXUSR)
|
|
666
667
|
perm |= c.X_OK;
|
|
667
668
|
}
|
|
668
|
-
if (credentials.
|
|
669
|
+
else if (credentials.egid === inode.gid || credentials.groups.includes(Number(inode.gid))) {
|
|
669
670
|
if (inode.mode & c.S_IRGRP)
|
|
670
671
|
perm |= c.R_OK;
|
|
671
672
|
if (inode.mode & c.S_IWGRP)
|
|
@@ -673,14 +674,64 @@ export function hasAccess($, inode, access) {
|
|
|
673
674
|
if (inode.mode & c.S_IXGRP)
|
|
674
675
|
perm |= c.X_OK;
|
|
675
676
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
677
|
+
else {
|
|
678
|
+
if (inode.mode & c.S_IROTH)
|
|
679
|
+
perm |= c.R_OK;
|
|
680
|
+
if (inode.mode & c.S_IWOTH)
|
|
681
|
+
perm |= c.W_OK;
|
|
682
|
+
if (inode.mode & c.S_IXOTH)
|
|
683
|
+
perm |= c.X_OK;
|
|
684
|
+
}
|
|
682
685
|
return (perm & access) === access;
|
|
683
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* Throws if a given user may not search the directory `dir`
|
|
689
|
+
* @internal
|
|
690
|
+
*/
|
|
691
|
+
export function assertSearchable($, dir, extra) {
|
|
692
|
+
if (checkAccess && !hasAccess($, dir, c.X_OK))
|
|
693
|
+
throw UV('EACCES', extra);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Throws if a given user may not remove `entry` from the directory `parent`
|
|
697
|
+
* @internal
|
|
698
|
+
*/
|
|
699
|
+
export function assertRemovable($, parent, entry, extra) {
|
|
700
|
+
if (!checkAccess)
|
|
701
|
+
return;
|
|
702
|
+
if (!hasAccess($, parent, c.W_OK | c.X_OK))
|
|
703
|
+
throw UV('EACCES', extra);
|
|
704
|
+
const { euid } = contextOf($).credentials;
|
|
705
|
+
if (parent.mode & c.S_ISVTX && euid !== 0 && euid !== entry.uid && euid !== parent.uid)
|
|
706
|
+
throw UV('EPERM', extra);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Throws if a given user is neither root nor the owner of `inode`
|
|
710
|
+
* @internal
|
|
711
|
+
*/
|
|
712
|
+
export function assertOwner($, inode, extra) {
|
|
713
|
+
if (!checkAccess)
|
|
714
|
+
return;
|
|
715
|
+
const { euid } = contextOf($).credentials;
|
|
716
|
+
if (euid !== 0 && euid !== inode.uid)
|
|
717
|
+
throw UV('EPERM', extra);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Throws if a given user may not change the owner of `inode` to `uid` and `gid`, where `-1` leaves an ID unchanged
|
|
721
|
+
* @internal
|
|
722
|
+
*/
|
|
723
|
+
export function assertCanChown($, inode, uid, gid, extra) {
|
|
724
|
+
if (!checkAccess)
|
|
725
|
+
return;
|
|
726
|
+
const { euid, egid, groups } = contextOf($).credentials;
|
|
727
|
+
if (euid === 0)
|
|
728
|
+
return;
|
|
729
|
+
const isOwner = euid === inode.uid;
|
|
730
|
+
if (uid !== -1 && !(isOwner && uid === inode.uid))
|
|
731
|
+
throw UV('EPERM', extra);
|
|
732
|
+
if (gid !== -1 && !(isOwner && (gid === inode.gid || gid === egid || groups.includes(gid))))
|
|
733
|
+
throw UV('EPERM', extra);
|
|
734
|
+
}
|
|
684
735
|
/**
|
|
685
736
|
* @hidden @internal
|
|
686
737
|
*
|
|
@@ -696,5 +747,19 @@ export function _chown(stats, uid, gid) {
|
|
|
696
747
|
stats.gid = gid;
|
|
697
748
|
else
|
|
698
749
|
valid = false;
|
|
750
|
+
if (stats.mode !== undefined && !isDirectory({ mode: stats.mode })) {
|
|
751
|
+
stats.mode &= ~c.S_ISUID;
|
|
752
|
+
if (stats.mode & c.S_IXGRP)
|
|
753
|
+
stats.mode &= ~c.S_ISGID;
|
|
754
|
+
}
|
|
699
755
|
return valid;
|
|
700
756
|
}
|
|
757
|
+
/**
|
|
758
|
+
* @hidden @internal
|
|
759
|
+
*/
|
|
760
|
+
export function _chmod($, inode, mode) {
|
|
761
|
+
const { euid, egid, groups } = contextOf($).credentials;
|
|
762
|
+
if (checkAccess && euid !== 0 && egid !== inode.gid && !groups.includes(inode.gid))
|
|
763
|
+
mode &= ~c.S_ISGID;
|
|
764
|
+
inode.mode = (inode.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
|
|
765
|
+
}
|
package/dist/mixins/async.js
CHANGED
|
@@ -57,6 +57,12 @@ import { StoreFS } from '../backends/store/fs.js';
|
|
|
57
57
|
import { isDirectory } from '../internal/inode.js';
|
|
58
58
|
import { join } from '../path.js';
|
|
59
59
|
import { _asyncFSKeys } from './shared.js';
|
|
60
|
+
/**
|
|
61
|
+
* Whether the stack of `error` has a patched method or `key`'s sync method below the innermost patched method.
|
|
62
|
+
*/
|
|
63
|
+
function isInLoop(key, error = new Error()) {
|
|
64
|
+
return new RegExp(`__patched__[^]*(__patched__|\\b${key}Sync\\b)`).test(error.stack ?? '');
|
|
65
|
+
}
|
|
60
66
|
/**
|
|
61
67
|
* Async() implements synchronous methods on an asynchronous file system
|
|
62
68
|
*
|
|
@@ -82,6 +88,8 @@ export function Async(FS) {
|
|
|
82
88
|
return this.sync();
|
|
83
89
|
}
|
|
84
90
|
_promise = Promise.resolve();
|
|
91
|
+
/** The async methods before patching, which do not update the sync cache */
|
|
92
|
+
_unpatched = {};
|
|
85
93
|
_async(thunk) {
|
|
86
94
|
this._promise = this._promise.finally(() => thunk());
|
|
87
95
|
}
|
|
@@ -132,7 +140,7 @@ export function Async(FS) {
|
|
|
132
140
|
renameSync(oldPath, newPath) {
|
|
133
141
|
this.checkSync();
|
|
134
142
|
this._sync.renameSync(oldPath, newPath);
|
|
135
|
-
this._async(() => this.rename(oldPath, newPath));
|
|
143
|
+
this._async(() => this._unpatched.rename(oldPath, newPath));
|
|
136
144
|
}
|
|
137
145
|
statSync(path) {
|
|
138
146
|
this.checkSync();
|
|
@@ -141,28 +149,28 @@ export function Async(FS) {
|
|
|
141
149
|
touchSync(path, metadata) {
|
|
142
150
|
this.checkSync();
|
|
143
151
|
this._sync.touchSync(path, metadata);
|
|
144
|
-
this._async(() => this.touch(path, metadata));
|
|
152
|
+
this._async(() => this._unpatched.touch(path, metadata));
|
|
145
153
|
}
|
|
146
154
|
createFileSync(path, options) {
|
|
147
155
|
this.checkSync();
|
|
148
156
|
const result = this._sync.createFileSync(path, options);
|
|
149
|
-
this._async(() => this.createFile(path, options));
|
|
157
|
+
this._async(() => this._unpatched.createFile(path, options));
|
|
150
158
|
return result;
|
|
151
159
|
}
|
|
152
160
|
unlinkSync(path) {
|
|
153
161
|
this.checkSync();
|
|
154
162
|
this._sync.unlinkSync(path);
|
|
155
|
-
this._async(() => this.unlink(path));
|
|
163
|
+
this._async(() => this._unpatched.unlink(path));
|
|
156
164
|
}
|
|
157
165
|
rmdirSync(path) {
|
|
158
166
|
this.checkSync();
|
|
159
167
|
this._sync.rmdirSync(path);
|
|
160
|
-
this._async(() => this.rmdir(path));
|
|
168
|
+
this._async(() => this._unpatched.rmdir(path));
|
|
161
169
|
}
|
|
162
170
|
mkdirSync(path, options) {
|
|
163
171
|
this.checkSync();
|
|
164
172
|
const result = this._sync.mkdirSync(path, options);
|
|
165
|
-
this._async(() => this.mkdir(path, options));
|
|
173
|
+
this._async(() => this._unpatched.mkdir(path, options));
|
|
166
174
|
return result;
|
|
167
175
|
}
|
|
168
176
|
readdirSync(path) {
|
|
@@ -172,7 +180,7 @@ export function Async(FS) {
|
|
|
172
180
|
linkSync(srcpath, dstpath) {
|
|
173
181
|
this.checkSync();
|
|
174
182
|
this._sync.linkSync(srcpath, dstpath);
|
|
175
|
-
this._async(() => this.link(srcpath, dstpath));
|
|
183
|
+
this._async(() => this._unpatched.link(srcpath, dstpath));
|
|
176
184
|
}
|
|
177
185
|
async sync() {
|
|
178
186
|
if (!this.attributes.has('no_async_preload') && this._sync)
|
|
@@ -194,7 +202,7 @@ export function Async(FS) {
|
|
|
194
202
|
writeSync(path, buffer, offset) {
|
|
195
203
|
this.checkSync();
|
|
196
204
|
this._sync.writeSync(path, buffer, offset);
|
|
197
|
-
this._async(() => this.write(path, buffer, offset));
|
|
205
|
+
this._async(() => this._unpatched.write(path, buffer, offset));
|
|
198
206
|
}
|
|
199
207
|
ioctlSync(context, command, ...args) {
|
|
200
208
|
this.checkSync();
|
|
@@ -261,22 +269,9 @@ export function Async(FS) {
|
|
|
261
269
|
for (const key of toPatch) {
|
|
262
270
|
// TS does not narrow the union based on the key
|
|
263
271
|
const originalMethod = this[key].bind(this);
|
|
264
|
-
function
|
|
265
|
-
if (!error) {
|
|
266
|
-
error = new Error();
|
|
267
|
-
Error.captureStackTrace(error, isInLoop);
|
|
268
|
-
}
|
|
269
|
-
if (!error.stack)
|
|
270
|
-
return false;
|
|
271
|
-
const stack = error.stack.split('\n').slice(depth).join('\n');
|
|
272
|
-
// From the async queue
|
|
273
|
-
return (stack.includes(`at <computed> [as ${key}]`)
|
|
274
|
-
|| stack.includes(`at async <computed> [as ${key}]`)
|
|
275
|
-
|| stack.includes(`${key}Sync `));
|
|
276
|
-
}
|
|
277
|
-
this[key] = async (...args) => {
|
|
272
|
+
const fn = async function __patched__(...args) {
|
|
278
273
|
const result = await originalMethod(...args);
|
|
279
|
-
if (isInLoop(
|
|
274
|
+
if (isInLoop(key))
|
|
280
275
|
return result;
|
|
281
276
|
if (!this._isInitialized) {
|
|
282
277
|
this._skippedCacheUpdates++;
|
|
@@ -287,13 +282,16 @@ export function Async(FS) {
|
|
|
287
282
|
this._sync?.[`${key}Sync`]?.(...args);
|
|
288
283
|
}
|
|
289
284
|
catch (e) {
|
|
290
|
-
if (isInLoop(
|
|
285
|
+
if (isInLoop(key, e))
|
|
291
286
|
return result;
|
|
292
287
|
e.message += ' (Out of sync!)';
|
|
293
288
|
throw err(e);
|
|
294
289
|
}
|
|
295
290
|
return result;
|
|
296
291
|
};
|
|
292
|
+
Object.defineProperty(fn, 'name', { get: () => key });
|
|
293
|
+
this._unpatched[key] = originalMethod;
|
|
294
|
+
this[key] = fn;
|
|
297
295
|
}
|
|
298
296
|
debug(`Async: patched ${toPatch.length} methods`);
|
|
299
297
|
}
|
package/dist/node/promises.js
CHANGED
|
@@ -55,7 +55,7 @@ import { Exception, rethrow, UV } from 'kerium';
|
|
|
55
55
|
import { encodeUTF8 } from 'utilium';
|
|
56
56
|
import * as constants from '../constants.js';
|
|
57
57
|
import { contextOf } from '../internal/contexts.js';
|
|
58
|
-
import { hasAccess, InodeFlags, isDirectory } from '../internal/inode.js';
|
|
58
|
+
import { assertRemovable, hasAccess, InodeFlags, isDirectory } from '../internal/inode.js';
|
|
59
59
|
import { dirname, join, matchesGlob, relative, resolve } from '../path.js';
|
|
60
60
|
import '../polyfills.js';
|
|
61
61
|
import { _isNoEntry, _tempDirName, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime, validateFD } from '../utils.js';
|
|
@@ -63,8 +63,7 @@ import * as _async from '../vfs/async.js';
|
|
|
63
63
|
import { checkAccess } from '../vfs/config.js';
|
|
64
64
|
import { deleteFD, fromFD, toFD } from '../vfs/file.js';
|
|
65
65
|
import * as flags from '../vfs/flags.js';
|
|
66
|
-
import { _statfs
|
|
67
|
-
import { cacheOf, lockPath } from '../vfs/vcache.js';
|
|
66
|
+
import { _statfs } from '../vfs/shared.js';
|
|
68
67
|
import { emitChange, FSWatcher } from '../vfs/watchers.js';
|
|
69
68
|
import { Dir, Dirent } from './dir.js';
|
|
70
69
|
import { createInterface } from './readline.js';
|
|
@@ -535,7 +534,7 @@ export async function exists(path) {
|
|
|
535
534
|
return await fs.exists(resolved);
|
|
536
535
|
}
|
|
537
536
|
catch (e) {
|
|
538
|
-
if (e instanceof Exception && e.code == 'ENOENT') {
|
|
537
|
+
if (e instanceof Exception && (e.code == 'ENOENT' || e.code == 'ENOTDIR' || e.code == 'EACCES')) {
|
|
539
538
|
return false;
|
|
540
539
|
}
|
|
541
540
|
throw e;
|
|
@@ -580,14 +579,13 @@ export async function unlink(path) {
|
|
|
580
579
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
581
580
|
try {
|
|
582
581
|
path = normalizePath.call(this, path);
|
|
583
|
-
const { fs, path: resolved } = resolveMount(path, this);
|
|
584
582
|
const $ex = { syscall: 'unlink', path };
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const _ = __addDisposableResource(env_2, await
|
|
583
|
+
const { fs, cache, path: resolved } = await _async.resolve(this, path, true, $ex);
|
|
584
|
+
const stats = await cache.stat(resolved).catch(rethrow($ex));
|
|
585
|
+
assertRemovable(this, await cache.stat(dirname(resolved)).catch(rethrow($ex)), stats, $ex);
|
|
586
|
+
const _ = __addDisposableResource(env_2, await cache.lock(dirname(resolved), 'rw'), false);
|
|
589
587
|
await fs.unlink(resolved).catch(rethrow($ex));
|
|
590
|
-
|
|
588
|
+
cache.remove(resolved);
|
|
591
589
|
emitChange(this, 'rename', path.toString());
|
|
592
590
|
}
|
|
593
591
|
catch (e_2) {
|
|
@@ -692,18 +690,17 @@ export async function rmdir(path) {
|
|
|
692
690
|
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
693
691
|
try {
|
|
694
692
|
path = normalizePath.call(this, path);
|
|
695
|
-
const { fs, path: resolved } = await _async.resolve(this, path);
|
|
693
|
+
const { fs, cache, path: resolved } = await _async.resolve(this, path, true);
|
|
696
694
|
const $ex = { syscall: 'rmdir', path };
|
|
697
|
-
const stats = await
|
|
695
|
+
const stats = await cache.stat(resolved).catch(rethrow($ex));
|
|
698
696
|
if (!stats)
|
|
699
697
|
throw UV('ENOENT', $ex);
|
|
700
698
|
if (!isDirectory(stats))
|
|
701
699
|
throw UV('ENOTDIR', $ex);
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const _ = __addDisposableResource(env_6, await lockPath(fs, dirname(resolved), 'rw'), false);
|
|
700
|
+
assertRemovable(this, await cache.stat(dirname(resolved)).catch(rethrow($ex)), stats, $ex);
|
|
701
|
+
const _ = __addDisposableResource(env_6, await cache.lock(dirname(resolved), 'rw'), false);
|
|
705
702
|
await fs.rmdir(resolved).catch(rethrow($ex));
|
|
706
|
-
|
|
703
|
+
cache.remove(resolved);
|
|
707
704
|
emitChange(this, 'rename', path.toString());
|
|
708
705
|
}
|
|
709
706
|
catch (e_6) {
|
|
@@ -761,7 +758,7 @@ export async function symlink(dest, path, type = 'file') {
|
|
|
761
758
|
const handle = __addDisposableResource(env_7, await _async.open(this, path, { flag: 'w+', mode: 0o644, preserveSymlinks: true }), true);
|
|
762
759
|
const encoded = encodeUTF8(normalizePath.call(this, dest, true));
|
|
763
760
|
await handle.write(encoded, 0, encoded.length, 0);
|
|
764
|
-
await handle.chmod(constants.S_IFLNK);
|
|
761
|
+
await handle.chmod(constants.S_IFLNK | 0o777);
|
|
765
762
|
}
|
|
766
763
|
catch (e_7) {
|
|
767
764
|
env_7.error = e_7;
|
|
@@ -1079,8 +1076,7 @@ export async function cp(source, destination, opts) {
|
|
|
1079
1076
|
const [entries] = await Promise.all([
|
|
1080
1077
|
readdir.call(this, source, { withFileTypes: true }),
|
|
1081
1078
|
mkdir.call(this, destination, { recursive: true }),
|
|
1082
|
-
]
|
|
1083
|
-
);
|
|
1079
|
+
]);
|
|
1084
1080
|
const _cp = async (dirent) => {
|
|
1085
1081
|
if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) {
|
|
1086
1082
|
return; // Skip if the filter returns false
|
|
@@ -1117,8 +1113,8 @@ export async function cp(source, destination, opts) {
|
|
|
1117
1113
|
cp;
|
|
1118
1114
|
export async function statfs(path, opts) {
|
|
1119
1115
|
path = normalizePath.call(this, path);
|
|
1120
|
-
const { fs } =
|
|
1121
|
-
return
|
|
1116
|
+
const { fs } = await _async.resolve(this, path);
|
|
1117
|
+
return _statfs(fs, opts?.bigint);
|
|
1122
1118
|
}
|
|
1123
1119
|
export function glob(pattern, opt) {
|
|
1124
1120
|
pattern = Array.isArray(pattern) ? pattern : [pattern];
|
package/dist/node/stats.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
|
|
|
65
65
|
/** @internal Used by inodes */
|
|
66
66
|
version?: number;
|
|
67
67
|
/** Creates a new stats instance from a stats-like object. Can be used to copy stats (note) */
|
|
68
|
-
constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, ...rest }?: Partial<InodeLike<number | bigint>>);
|
|
68
|
+
constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, rdev, dev, blksize, ...rest }?: Partial<InodeLike<number | bigint> & StatsExtra<number | bigint>>);
|
|
69
69
|
isFile(): boolean;
|
|
70
70
|
isDirectory(): boolean;
|
|
71
71
|
isSymbolicLink(): boolean;
|
package/dist/node/stats.js
CHANGED
|
@@ -96,7 +96,7 @@ export class StatsCommon {
|
|
|
96
96
|
/** @internal Used by inodes */
|
|
97
97
|
version;
|
|
98
98
|
/** Creates a new stats instance from a stats-like object. Can be used to copy stats (note) */
|
|
99
|
-
constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, ...rest } = {}) {
|
|
99
|
+
constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, rdev, dev, blksize, ...rest } = {}) {
|
|
100
100
|
const now = Date.now();
|
|
101
101
|
this.atimeMs = this._convert(atimeMs ?? now);
|
|
102
102
|
this.mtimeMs = this._convert(mtimeMs ?? now);
|
|
@@ -107,6 +107,9 @@ export class StatsCommon {
|
|
|
107
107
|
this.size = this._convert(size ?? 0);
|
|
108
108
|
this.ino = this._convert(ino ?? 0);
|
|
109
109
|
this.mode = this._convert(mode ?? 0o644 & c.S_IFREG);
|
|
110
|
+
this.rdev = this._convert(rdev ?? 0);
|
|
111
|
+
this.dev = this._convert(dev ?? 0);
|
|
112
|
+
this.blksize = this._convert(blksize ?? 4096);
|
|
110
113
|
if ((this.mode & c.S_IFMT) == 0) {
|
|
111
114
|
this.mode = (this.mode | this._convert(c.S_IFREG));
|
|
112
115
|
}
|
package/dist/node/sync.js
CHANGED
|
@@ -56,15 +56,14 @@ import { encodeUTF8 } from 'utilium';
|
|
|
56
56
|
import * as constants from '../constants.js';
|
|
57
57
|
import { contextOf } from '../internal/contexts.js';
|
|
58
58
|
import { wrap } from '../internal/error.js';
|
|
59
|
-
import { hasAccess, isDirectory } from '../internal/inode.js';
|
|
59
|
+
import { assertRemovable, hasAccess, isDirectory } from '../internal/inode.js';
|
|
60
60
|
import { dirname, join, matchesGlob, relative, resolve } from '../path.js';
|
|
61
61
|
import { _isNoEntry, _tempDirName, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
|
|
62
62
|
import { checkAccess } from '../vfs/config.js';
|
|
63
63
|
import { deleteFD, fromFD, toFD } from '../vfs/file.js';
|
|
64
64
|
import * as flags from '../vfs/flags.js';
|
|
65
|
-
import { _statfs
|
|
65
|
+
import { _statfs } from '../vfs/shared.js';
|
|
66
66
|
import * as _sync from '../vfs/sync.js';
|
|
67
|
-
import { cacheOf, lockPathSync } from '../vfs/vcache.js';
|
|
68
67
|
import { emitChange } from '../vfs/watchers.js';
|
|
69
68
|
import { Dir, Dirent } from './dir.js';
|
|
70
69
|
import { BigIntStats, Stats } from './stats.js';
|
|
@@ -82,7 +81,7 @@ export function existsSync(path) {
|
|
|
82
81
|
return fs.existsSync(resolvedPath);
|
|
83
82
|
}
|
|
84
83
|
catch (e) {
|
|
85
|
-
if (e.errno == Errno.ENOENT)
|
|
84
|
+
if (e.errno == Errno.ENOENT || e.errno == Errno.ENOTDIR || e.errno == Errno.EACCES)
|
|
86
85
|
return false;
|
|
87
86
|
throw e;
|
|
88
87
|
}
|
|
@@ -134,16 +133,14 @@ export function truncateSync(path, len = 0) {
|
|
|
134
133
|
truncateSync;
|
|
135
134
|
export function unlinkSync(path) {
|
|
136
135
|
path = normalizePath.call(this, path);
|
|
137
|
-
const { fs, path: resolved } =
|
|
136
|
+
const { fs, cache, path: resolved } = _sync.resolve(this, path, true, { syscall: 'unlink', path });
|
|
138
137
|
try {
|
|
139
138
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
140
139
|
try {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
const _ = __addDisposableResource(env_2, lockPathSync(fs, dirname(resolved), 'rw'), false);
|
|
140
|
+
assertRemovable(this, cache.statSync(dirname(resolved)), cache.statSync(resolved));
|
|
141
|
+
const _ = __addDisposableResource(env_2, cache.lockSync(dirname(resolved), 'rw'), false);
|
|
145
142
|
fs.unlinkSync(resolved);
|
|
146
|
-
|
|
143
|
+
cache.remove(resolved);
|
|
147
144
|
}
|
|
148
145
|
catch (e_2) {
|
|
149
146
|
env_2.error = e_2;
|
|
@@ -372,15 +369,14 @@ export function rmdirSync(path) {
|
|
|
372
369
|
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
373
370
|
try {
|
|
374
371
|
path = normalizePath.call(this, path);
|
|
375
|
-
const { fs, path: resolved } = _sync.resolve(this, path);
|
|
376
|
-
const stats =
|
|
372
|
+
const { fs, cache, path: resolved } = _sync.resolve(this, path, true);
|
|
373
|
+
const stats = cache.statSync(resolved, { path, syscall: 'rmdir' });
|
|
377
374
|
if (!isDirectory(stats))
|
|
378
375
|
throw UV('ENOTDIR', 'rmdir', path);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const _ = __addDisposableResource(env_5, lockPathSync(fs, dirname(resolved), 'rw'), false);
|
|
376
|
+
assertRemovable(this, cache.statSync(dirname(resolved), { path, syscall: 'rmdir' }), stats, { syscall: 'rmdir', path });
|
|
377
|
+
const _ = __addDisposableResource(env_5, cache.lockSync(dirname(resolved), 'rw'), false);
|
|
382
378
|
wrap(fs, 'rmdirSync', path)(resolved);
|
|
383
|
-
|
|
379
|
+
cache.remove(resolved);
|
|
384
380
|
emitChange(this, 'rename', path.toString());
|
|
385
381
|
}
|
|
386
382
|
catch (e_5) {
|
|
@@ -435,7 +431,7 @@ export function symlinkSync(target, path, type = 'file') {
|
|
|
435
431
|
path = normalizePath.call(this, path);
|
|
436
432
|
const file = __addDisposableResource(env_6, _sync.open(this, path, { flag: 'wx', mode: 0o644 }), false);
|
|
437
433
|
file.writeSync(encodeUTF8(normalizePath.call(this, target, true)));
|
|
438
|
-
file.chmodSync(constants.S_IFLNK);
|
|
434
|
+
file.chmodSync(constants.S_IFLNK | 0o777);
|
|
439
435
|
}
|
|
440
436
|
catch (e_6) {
|
|
441
437
|
env_6.error = e_6;
|
|
@@ -706,7 +702,7 @@ export function cpSync(source, destination, opts) {
|
|
|
706
702
|
cpSync;
|
|
707
703
|
export function statfsSync(path, options) {
|
|
708
704
|
path = normalizePath.call(this, path);
|
|
709
|
-
const { fs } =
|
|
705
|
+
const { fs } = _sync.resolve(this, path);
|
|
710
706
|
return _statfs(fs, options?.bigint);
|
|
711
707
|
}
|
|
712
708
|
export function globSync(pattern, options = {}) {
|
package/dist/polyfills.js
CHANGED
|
@@ -66,4 +66,20 @@ Array.fromAsync ??=
|
|
|
66
66
|
}
|
|
67
67
|
return results;
|
|
68
68
|
});
|
|
69
|
+
Map.prototype.getOrInsert ??=
|
|
70
|
+
(warn('Using a polyfill of Map.prototype.getOrInsert'),
|
|
71
|
+
function getOrInsert(key, defaultValue) {
|
|
72
|
+
if (!this.has(key))
|
|
73
|
+
this.set(key, defaultValue);
|
|
74
|
+
return this.get(key);
|
|
75
|
+
});
|
|
76
|
+
Map.prototype.getOrInsertComputed ??=
|
|
77
|
+
(warn('Using a polyfill of Map.prototype.getOrInsertComputed'),
|
|
78
|
+
function getOrInsertComputed(key, callback) {
|
|
79
|
+
if (this.has(key))
|
|
80
|
+
return this.get(key);
|
|
81
|
+
const value = callback(key);
|
|
82
|
+
this.set(key, value);
|
|
83
|
+
return value;
|
|
84
|
+
});
|
|
69
85
|
/* node:coverage enable */
|