@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.
Files changed (40) hide show
  1. package/dist/backends/store/fs.js +1 -3
  2. package/dist/backends/store/store.js +1 -3
  3. package/dist/config.d.ts +7 -0
  4. package/dist/config.js +9 -3
  5. package/dist/internal/error.d.ts +5 -0
  6. package/dist/internal/error.js +10 -0
  7. package/dist/internal/inode.d.ts +25 -0
  8. package/dist/internal/inode.js +75 -10
  9. package/dist/mixins/async.js +22 -24
  10. package/dist/node/promises.js +17 -21
  11. package/dist/node/stats.d.ts +1 -1
  12. package/dist/node/stats.js +4 -1
  13. package/dist/node/sync.js +14 -18
  14. package/dist/polyfills.js +16 -0
  15. package/dist/vfs/async.js +77 -64
  16. package/dist/vfs/config.d.ts +10 -1
  17. package/dist/vfs/config.js +5 -2
  18. package/dist/vfs/file.js +10 -4
  19. package/dist/vfs/ioctl.js +2 -2
  20. package/dist/vfs/shared.d.ts +2 -0
  21. package/dist/vfs/shared.js +3 -4
  22. package/dist/vfs/sync.js +77 -60
  23. package/dist/vfs/vcache.d.ts +14 -14
  24. package/dist/vfs/vcache.js +65 -57
  25. package/dist/vfs/watchers.d.ts +1 -0
  26. package/dist/vfs/watchers.js +2 -4
  27. package/dist/vfs/xattr.js +18 -17
  28. package/package.json +2 -3
  29. package/scripts/make-index.js +1 -1
  30. package/tests/common/async.test.ts +35 -0
  31. package/tests/common/config.test.ts +1 -1
  32. package/tests/common/file_index.test.ts +1 -1
  33. package/tests/common/mounts.test.ts +12 -0
  34. package/tests/common/vcache.test.ts +13 -0
  35. package/tests/fs/concurrency.test.ts +3 -3
  36. package/tests/fs/links.test.ts +76 -0
  37. package/tests/fs/permissions.test.ts +232 -0
  38. package/tests/fs/stat.test.ts +1 -1
  39. package/tests/fs/streams.test.ts +1 -1
  40. 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
- if (!this._paths.has(ino))
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
- if (!this.originalData.has(id))
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 { _setAccessChecks } from './vfs/config.js';
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
- _setAccessChecks(!configuration.disableAccessChecks);
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
- _setAccessChecks(!configuration.disableAccessChecks);
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) {
@@ -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;
@@ -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
+ }
@@ -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 {};
@@ -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 || credentials.egid === 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.uid === inode.uid) {
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.gid === inode.gid || credentials.groups.includes(Number(inode.gid))) {
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
- if (inode.mode & c.S_IROTH)
677
- perm |= c.R_OK;
678
- if (inode.mode & c.S_IWOTH)
679
- perm |= c.W_OK;
680
- if (inode.mode & c.S_IXOTH)
681
- perm |= c.X_OK;
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
+ }
@@ -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 isInLoop(depth, error) {
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(2))
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(3, e))
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
  }
@@ -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, resolveMount } from '../vfs/shared.js';
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 stats = await fs.stat(resolved).catch(rethrow($ex));
586
- if (checkAccess && !hasAccess(this, stats, constants.W_OK))
587
- throw UV('EACCES', $ex);
588
- const _ = __addDisposableResource(env_2, await lockPath(fs, dirname(resolved), 'rw'), false);
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
- cacheOf(fs).remove(resolved);
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 fs.stat(resolved).catch(rethrow($ex));
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
- if (checkAccess && !hasAccess(this, stats, constants.W_OK))
703
- throw UV('EACCES', $ex);
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
- cacheOf(fs).remove(resolved);
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
- ] // Ensure the destination directory exists
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 } = resolveMount(path, this);
1121
- return Promise.resolve(_statfs(fs, opts?.bigint));
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];
@@ -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;
@@ -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, resolveMount } from '../vfs/shared.js';
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 } = resolveMount(path, this);
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
- if (checkAccess && !hasAccess(this, fs.statSync(resolved), constants.W_OK)) {
142
- throw UV('EACCES', 'unlink');
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
- cacheOf(fs).remove(resolved);
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 = wrap(fs, 'statSync', { path, syscall: 'rmdir' })(resolved);
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
- if (checkAccess && !hasAccess(this, stats, constants.W_OK))
380
- throw UV('EACCES', 'rmdir', path);
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
- cacheOf(fs).remove(resolved);
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 } = resolveMount(path, this);
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 */