@zenfs/core 2.7.6 → 2.8.0

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 (70) hide show
  1. package/README.md +5 -0
  2. package/dist/backends/index.d.ts +3 -1
  3. package/dist/backends/index.js +3 -1
  4. package/dist/backends/single_buffer/backend.d.ts +42 -0
  5. package/dist/backends/single_buffer/backend.js +204 -0
  6. package/dist/backends/{single_buffer.d.ts → single_buffer/v1.d.ts} +24 -34
  7. package/dist/backends/{single_buffer.js → single_buffer/v1.js} +10 -26
  8. package/dist/backends/single_buffer/v2.d.ts +257 -0
  9. package/dist/backends/single_buffer/v2.js +1283 -0
  10. package/dist/backends/store/fs.d.ts +1 -1
  11. package/dist/backends/store/fs.js +35 -18
  12. package/dist/backends/store/map.d.ts +6 -6
  13. package/dist/backends/store/map.js +6 -6
  14. package/dist/backends/store/store.d.ts +18 -14
  15. package/dist/backends/store/store.js +12 -14
  16. package/dist/config.d.ts +7 -0
  17. package/dist/config.js +9 -3
  18. package/dist/internal/contexts.d.ts +5 -0
  19. package/dist/internal/contexts.js +7 -1
  20. package/dist/internal/error.d.ts +5 -0
  21. package/dist/internal/error.js +10 -0
  22. package/dist/internal/inode.d.ts +25 -0
  23. package/dist/internal/inode.js +75 -10
  24. package/dist/mixins/async.js +47 -30
  25. package/dist/node/promises.js +23 -124
  26. package/dist/node/stats.d.ts +1 -1
  27. package/dist/node/stats.js +4 -1
  28. package/dist/node/sync.js +20 -51
  29. package/dist/polyfills.js +16 -0
  30. package/dist/vfs/async.d.ts +4 -1
  31. package/dist/vfs/async.js +149 -69
  32. package/dist/vfs/config.d.ts +10 -1
  33. package/dist/vfs/config.js +5 -2
  34. package/dist/vfs/file.js +10 -4
  35. package/dist/vfs/ioctl.js +2 -2
  36. package/dist/vfs/shared.d.ts +2 -0
  37. package/dist/vfs/shared.js +3 -4
  38. package/dist/vfs/sync.d.ts +4 -1
  39. package/dist/vfs/sync.js +142 -64
  40. package/dist/vfs/vcache.d.ts +14 -14
  41. package/dist/vfs/vcache.js +65 -57
  42. package/dist/vfs/watchers.d.ts +1 -0
  43. package/dist/vfs/watchers.js +2 -4
  44. package/dist/vfs/xattr.js +18 -17
  45. package/package.json +3 -4
  46. package/scripts/make-index.js +1 -1
  47. package/tests/backend/single-buffer-rotate.worker.js +1 -1
  48. package/tests/backend/single-buffer-v2.test.ts +342 -0
  49. package/tests/backend/single-buffer-v2.worker.js +51 -0
  50. package/tests/backend/single-buffer.test.ts +6 -6
  51. package/tests/backend/single-buffer.worker.js +1 -0
  52. package/tests/common/async.test.ts +46 -0
  53. package/tests/common/config.test.ts +1 -1
  54. package/tests/common/context.test.ts +30 -0
  55. package/tests/common/file_index.test.ts +1 -1
  56. package/tests/common/mounts.test.ts +12 -0
  57. package/tests/common/vcache.test.ts +13 -0
  58. package/tests/fs/concurrency.test.ts +3 -3
  59. package/tests/fs/links.test.ts +76 -0
  60. package/tests/fs/permissions.test.ts +253 -0
  61. package/tests/fs/stat.test.ts +1 -1
  62. package/tests/fs/streams.test.ts +1 -1
  63. package/tests/fs/watch.test.ts +14 -0
  64. package/tests/fs/xattr.test.ts +10 -0
  65. package/tests/perf/config.json +31 -1
  66. package/tests/perf/single-buffer-random.ts +38 -0
  67. package/tests/perf/single-buffer-read.ts +28 -0
  68. package/tests/perf/single-buffer-write.ts +41 -0
  69. package/tests/setup/single-buffer-v2.ts +10 -0
  70. package/tests/setup/single-buffer.ts +1 -0
package/dist/vfs/sync.js CHANGED
@@ -53,13 +53,13 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
53
53
  import { setUVMessage, UV } from 'kerium';
54
54
  import { decodeUTF8 } from 'utilium';
55
55
  import * as constants from '../constants.js';
56
- import { contextOf } from '../internal/contexts.js';
56
+ import { applyUmask, contextOf } from '../internal/contexts.js';
57
57
  import { wrap } from '../internal/error.js';
58
- import { hasAccess, isDirectory, isSymbolicLink } from '../internal/inode.js';
58
+ import { assertRemovable, assertSearchable, hasAccess, isDirectory, isSymbolicLink } from '../internal/inode.js';
59
59
  import { basename, dirname, join, parse, resolve as resolvePath } from '../path.js';
60
- import { normalizeMode, normalizePath } from '../utils.js';
61
- import { cacheOf, lockPathSync } from './vcache.js';
62
- import { checkAccess } from './config.js';
60
+ import { normalizeMode, normalizePath, normalizeTime } from '../utils.js';
61
+ import { cacheOf } from './vcache.js';
62
+ import { checkAccess, resolveFullWalk } from './config.js';
63
63
  import { Dirent, ifToDt } from './dir.js';
64
64
  import { Handle } from './file.js';
65
65
  import * as flags from './flags.js';
@@ -75,35 +75,45 @@ export function resolve($, path, preserveSymlinks, extra) {
75
75
  path = resolvePath.call($, path);
76
76
  /* Try to resolve it directly. If this works,
77
77
  that means we don't need to perform any resolution for parent directories. */
78
- try {
79
- const resolved = resolveMount(path, $);
80
- // Stat it to make sure it exists. The vnode cache takes precedence since it may have unsynced changes
81
- const stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? resolved.fs.statSync(resolved.path);
82
- if (!isSymbolicLink(stats) || preserveSymlinks) {
83
- return { ...resolved, fullPath: path, stats };
78
+ if (!resolveFullWalk)
79
+ try {
80
+ const resolved = resolveMount(path, $);
81
+ // Stat it to make sure it exists. The vnode cache takes precedence since it may have unsynced changes
82
+ const stats = resolved.cache.statSync(resolved.path);
83
+ if (!isSymbolicLink(stats) || preserveSymlinks) {
84
+ return { ...resolved, fullPath: path, stats };
85
+ }
86
+ const target = resolvePath.call($, dirname(path), readlink.call($, path));
87
+ return resolve($, target, preserveSymlinks, extra);
84
88
  }
85
- const target = resolvePath.call($, dirname(path), readlink.call($, path));
86
- return resolve($, target, preserveSymlinks, extra);
89
+ catch {
90
+ // Go the long way
91
+ }
92
+ const { base, dir } = parse(path);
93
+ const $ex = { syscall: 'stat', path, ...extra };
94
+ let realDir = '/';
95
+ if (dir != '/') {
96
+ const parent = resolve($, dir, false, extra);
97
+ if (parent.stats && isDirectory(parent.stats))
98
+ assertSearchable($, parent.stats, $ex);
99
+ realDir = parent.fullPath;
87
100
  }
88
- catch (e) {
89
- setUVMessage(Object.assign(e, { syscall: 'stat', path, ...extra }));
90
- if (preserveSymlinks)
91
- throw e;
101
+ else if (base && checkAccess) {
102
+ const root = resolveMount('/', $);
103
+ assertSearchable($, root.cache.statSync(root.path), $ex);
92
104
  }
93
- const { base, dir } = parse(path);
94
- const realDir = dir == '/' ? '/' : resolve($, dir, false, extra).fullPath;
95
105
  const maybePath = join(realDir, base);
96
106
  const resolved = resolveMount(maybePath, $);
97
107
  let stats;
98
108
  try {
99
- stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? resolved.fs.statSync(resolved.path);
109
+ stats = resolved.cache.statSync(resolved.path);
100
110
  }
101
111
  catch (e) {
102
112
  if (e.code === 'ENOENT')
103
113
  return { ...resolved, fullPath: maybePath };
104
114
  throw setUVMessage(Object.assign(e, { syscall: 'stat', path: maybePath, ...extra }));
105
115
  }
106
- if (!isSymbolicLink(stats)) {
116
+ if (!isSymbolicLink(stats) || preserveSymlinks) {
107
117
  return { ...resolved, fullPath: maybePath, stats };
108
118
  }
109
119
  const target = resolvePath.call($, realDir, readlink.call($, maybePath));
@@ -116,11 +126,11 @@ export function resolve($, path, preserveSymlinks, extra) {
116
126
  export function open($, path, opt) {
117
127
  path = normalizePath.call($, path);
118
128
  const mode = normalizeMode(opt.mode, 0o644), flag = flags.parse(opt.flag);
119
- path = opt.preserveSymlinks ? path : resolve($, path).fullPath;
120
- const { fs, path: resolved } = resolveMount(path, $);
129
+ path = resolve($, path, opt.preserveSymlinks).fullPath;
130
+ const { fs, cache, path: resolved } = resolveMount(path, $);
121
131
  let stats;
122
132
  try {
123
- stats = fs.statSync(resolved);
133
+ stats = cache.statSync(resolved);
124
134
  }
125
135
  catch {
126
136
  // nothing
@@ -133,8 +143,8 @@ export function open($, path, opt) {
133
143
  }
134
144
  // Create the file
135
145
  const parentPath = dirname(resolved);
136
- const parentStats = fs.statSync(parentPath);
137
- if (checkAccess && !hasAccess($, parentStats, constants.W_OK)) {
146
+ const parentStats = cache.statSync(parentPath, { syscall: 'open', path });
147
+ if (checkAccess && !hasAccess($, parentStats, constants.W_OK | constants.X_OK)) {
138
148
  throw UV('EACCES', 'open', path);
139
149
  }
140
150
  if (!isDirectory(parentStats)) {
@@ -143,16 +153,16 @@ export function open($, path, opt) {
143
153
  if (!opt.allowDirectory && isDirectory({ mode }))
144
154
  throw UV('EISDIR', 'open', path);
145
155
  // Serialize entry creation with other operations on the parent directory
146
- const _ = __addDisposableResource(env_1, lockPathSync(fs, parentPath, 'rw', parentStats), false);
156
+ const _ = __addDisposableResource(env_1, cache.lockSync(parentPath, 'rw', parentStats), false);
147
157
  const { euid: uid, egid: gid } = contextOf($).credentials;
148
158
  const inode = fs.createFileSync(resolved, {
149
- mode,
159
+ mode: applyUmask($, mode),
150
160
  uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
151
161
  gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
152
162
  });
153
163
  // A new entry in the parent directory, which is a 'rename' event
154
164
  emitChange($, 'rename', path);
155
- return new Handle($, path, resolved, flag, cacheOf(fs).ref(resolved, inode));
165
+ return new Handle($, path, resolved, flag, cache.ref(resolved, inode));
156
166
  }
157
167
  catch (e_1) {
158
168
  env_1.error = e_1;
@@ -162,14 +172,14 @@ export function open($, path, opt) {
162
172
  __disposeResources(env_1);
163
173
  }
164
174
  }
165
- if (checkAccess && (!hasAccess($, stats, mode) || !hasAccess($, stats, flags.toMode(flag)))) {
175
+ if (checkAccess && !hasAccess($, stats, flags.toMode(flag))) {
166
176
  throw UV('EACCES', 'open', path);
167
177
  }
168
178
  if (flag & constants.O_EXCL)
169
179
  throw UV('EEXIST', 'open', path);
170
180
  if (!opt.allowDirectory && isDirectory(stats))
171
181
  throw UV('EISDIR', 'open', path);
172
- const file = new Handle($, path, resolved, flag, cacheOf(fs).ref(resolved, stats));
182
+ const file = new Handle($, path, resolved, flag, cache.ref(resolved, stats));
173
183
  if (flag & constants.O_TRUNC)
174
184
  file.truncateSync(0);
175
185
  return file;
@@ -192,20 +202,21 @@ export function mkdir(path, options = {}) {
192
202
  const original = normalizePath.call(this, path);
193
203
  const $ex = { syscall: 'mkdir', path: original };
194
204
  const { fullPath: realParent } = resolve(this, dirname(original), false, $ex);
195
- const { fs, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
205
+ const { fs, cache, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
196
206
  const followed = realParent != dirname(original);
197
207
  const { euid: uid, egid: gid } = contextOf(this).credentials;
198
- const { mode = 0o777, recursive } = options;
208
+ const { recursive } = options;
209
+ const mode = applyUmask(this, options.mode ?? 0o777);
199
210
  let firstCreated;
200
211
  const __create = (path, resolved) => {
201
212
  const env_2 = { stack: [], error: void 0, hasError: false };
202
213
  try {
203
214
  const parentPath = dirname(resolved);
204
- const parent = recursive && parentPath != '/' ? __create(dirname(path), parentPath) : fs.statSync(parentPath);
215
+ const parent = recursive && parentPath != '/' ? __create(dirname(path), parentPath) : cache.statSync(parentPath, $ex);
205
216
  if (recursive) {
206
217
  let existing;
207
218
  try {
208
- existing = cacheOf(fs).get(resolved)?.inode ?? fs.statSync(resolved);
219
+ existing = cache.statSync(resolved);
209
220
  }
210
221
  catch {
211
222
  // It doesn't exist yet
@@ -221,9 +232,9 @@ export function mkdir(path, options = {}) {
221
232
  if (followed && fs.existsSync(join(parentPath, basename(path))))
222
233
  throw UV('ENOENT', $ex);
223
234
  }
224
- if (checkAccess && !hasAccess(this, parent, constants.W_OK))
235
+ if (checkAccess && !hasAccess(this, parent, constants.W_OK | constants.X_OK))
225
236
  throw UV('EACCES', 'mkdir', path);
226
- const _ = __addDisposableResource(env_2, lockPathSync(fs, parentPath, 'rw', parent), false);
237
+ const _ = __addDisposableResource(env_2, cache.lockSync(parentPath, 'rw', parent), false);
227
238
  const inode = wrap(fs, 'mkdirSync', { path, syscall: 'mkdir' })(resolved, {
228
239
  mode,
229
240
  uid: parent.mode & constants.S_ISUID ? parent.uid : uid,
@@ -247,9 +258,9 @@ export function mkdir(path, options = {}) {
247
258
  }
248
259
  export function readdir(path, options = {}) {
249
260
  path = normalizePath.call(this, path);
250
- const { fs, path: resolved } = resolve(this, path);
261
+ const { fs, cache, path: resolved } = resolve(this, path);
251
262
  // Node reports `readdir` failures as `scandir`
252
- const stats = cacheOf(fs).get(resolved)?.inode ?? wrap(fs, 'statSync', { path, syscall: 'scandir' })(resolved);
263
+ const stats = cache.statSync(resolved, { path, syscall: 'scandir' });
253
264
  if (checkAccess && !hasAccess(this, stats, constants.R_OK))
254
265
  throw UV('EACCES', 'scandir', path);
255
266
  if (!isDirectory(stats))
@@ -258,7 +269,7 @@ export function readdir(path, options = {}) {
258
269
  {
259
270
  const env_3 = { stack: [], error: void 0, hasError: false };
260
271
  try {
261
- const _ = __addDisposableResource(env_3, lockPathSync(fs, resolved, 'ro', stats), false);
272
+ const _ = __addDisposableResource(env_3, cache.lockSync(resolved, 'ro', stats), false);
262
273
  entries = fs.readdirSync(resolved);
263
274
  }
264
275
  catch (e_3) {
@@ -273,7 +284,7 @@ export function readdir(path, options = {}) {
273
284
  const addEntry = (entry) => {
274
285
  let entryStat;
275
286
  try {
276
- entryStat = fs.statSync(join(resolved, entry));
287
+ entryStat = cache.statSync(join(resolved, entry), { syscall: 'scandir', path });
277
288
  }
278
289
  catch (e) {
279
290
  if (e.code == 'ENOENT')
@@ -303,7 +314,7 @@ export function rename(oldPath, newPath) {
303
314
  newPath = normalizePath.call(this, newPath);
304
315
  const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
305
316
  const src = resolve(this, oldPath, true, $ex);
306
- const dst = resolveMount(newPath, this, $ex);
317
+ const dst = resolve(this, newPath, true, $ex);
307
318
  if (src.fs.uuid !== dst.fs.uuid)
308
319
  throw UV('EXDEV', $ex);
309
320
  // A directory can not be moved inside itself
@@ -311,25 +322,29 @@ export function rename(oldPath, newPath) {
311
322
  throw UV('EINVAL', $ex);
312
323
  if (!src.stats)
313
324
  throw UV('ENOENT', $ex);
314
- const fs = src.fs;
315
- const oldParent = fs.statSync(dirname(src.path));
316
- const newParent = fs.statSync(dirname(dst.path));
325
+ const srcDir = dirname(src.path);
326
+ const dstDir = dirname(dst.path);
327
+ const oldParent = src.cache.statSync(srcDir, $ex);
328
+ const newParent = src.cache.statSync(dstDir, $ex);
317
329
  let newStats;
318
330
  try {
319
- newStats = fs.statSync(dst.path);
331
+ newStats = src.cache.statSync(dst.path, $ex);
320
332
  }
321
333
  catch (e) {
322
334
  if (e.code != 'ENOENT')
323
335
  throw e;
324
336
  }
325
- if (checkAccess && (!hasAccess(this, oldParent, constants.R_OK) || !hasAccess(this, newParent, constants.W_OK)))
337
+ assertRemovable(this, oldParent, src.stats, $ex);
338
+ if (newStats)
339
+ assertRemovable(this, newParent, newStats, $ex);
340
+ if (checkAccess && !hasAccess(this, newParent, constants.W_OK | constants.X_OK))
341
+ throw UV('EACCES', $ex);
342
+ if (checkAccess && isDirectory(src.stats) && srcDir != dstDir && !hasAccess(this, src.stats, constants.W_OK))
326
343
  throw UV('EACCES', $ex);
327
344
  if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
328
345
  throw UV('EISDIR', $ex);
329
346
  if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
330
347
  throw UV('ENOTDIR', $ex);
331
- const srcDir = dirname(src.path);
332
- const dstDir = dirname(dst.path);
333
348
  // Lock both parent directories, ordered by inode number to avoid ABBA deadlocks (like Linux's `lock_two_nondirectories`)
334
349
  const parents = [
335
350
  [srcDir, oldParent],
@@ -337,10 +352,10 @@ export function rename(oldPath, newPath) {
337
352
  ];
338
353
  if (oldParent.ino > newParent.ino)
339
354
  parents.reverse();
340
- const _first = __addDisposableResource(env_4, lockPathSync(fs, parents[0][0], 'rw', parents[0][1]), false);
341
- const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : lockPathSync(fs, parents[1][0], 'rw', parents[1][1]), false);
355
+ const _first = __addDisposableResource(env_4, src.cache.lockSync(parents[0][0], 'rw', parents[0][1]), false);
356
+ const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : src.cache.lockSync(parents[1][0], 'rw', parents[1][1]), false);
342
357
  src.fs.renameSync(src.path, dst.path);
343
- cacheOf(fs).rename(src.path, dst.path);
358
+ src.cache.rename(src.path, dst.path);
344
359
  // Both names change which entries exist, so both are 'rename' events
345
360
  emitChange(this, 'rename', oldPath);
346
361
  emitChange(this, 'rename', newPath);
@@ -359,24 +374,24 @@ export function link(target, link) {
359
374
  target = normalizePath.call(this, target);
360
375
  link = normalizePath.call(this, link);
361
376
  const $ex = { syscall: 'link', path: target, dest: link };
362
- const { fs, path: resolved } = resolveMount(target, this, $ex);
363
- const dst = resolveMount(link, this, $ex);
377
+ const { fs, cache, path: resolved } = resolve(this, target, true, $ex);
378
+ const dst = resolve(this, link, true, $ex);
364
379
  if (fs.uuid !== dst.fs.uuid)
365
380
  throw UV('EXDEV', $ex);
366
- const stats = fs.statSync(resolved);
381
+ const stats = cache.statSync(resolved, $ex);
367
382
  if (checkAccess) {
368
383
  if (!hasAccess(this, stats, constants.R_OK))
369
384
  throw UV('EACCES', $ex);
370
- const dirStats = fs.statSync(dirname(resolved));
385
+ const dirStats = cache.statSync(dirname(resolved), $ex);
371
386
  if (!hasAccess(this, dirStats, constants.R_OK))
372
387
  throw UV('EACCES', $ex);
373
- const destStats = fs.statSync(dirname(dst.path));
388
+ const destStats = cache.statSync(dirname(dst.path), $ex);
374
389
  if (!hasAccess(this, destStats, constants.W_OK))
375
390
  throw UV('EACCES', $ex);
376
391
  }
377
- const _ = __addDisposableResource(env_5, lockPathSync(fs, dirname(dst.path), 'rw'), false);
392
+ const _ = __addDisposableResource(env_5, cache.lockSync(dirname(dst.path), 'rw'), false);
378
393
  fs.linkSync(resolved, dst.path);
379
- cacheOf(fs).link(resolved, dst.path);
394
+ cache.link(resolved, dst.path);
380
395
  }
381
396
  catch (e_5) {
382
397
  env_5.error = e_5;
@@ -394,10 +409,15 @@ export function stat(path, lstat) {
394
409
  stats = resolve(this, path, false, extra).stats;
395
410
  else {
396
411
  const { base, dir } = parse(path);
397
- const { fs, path: parent } = resolve(this, dir, false, extra);
412
+ const parent = resolve(this, dir, false, extra);
413
+ if (parent.stats && isDirectory(parent.stats))
414
+ assertSearchable(this, parent.stats, extra);
415
+ const { root, mounts } = contextOf(this);
416
+ const mounted = base && mounts.get(join(root, parent.fullPath, base));
417
+ const fs = mounted || parent.fs;
418
+ const target = mounted ? '/' : join(parent.path, base);
398
419
  try {
399
- const target = base ? join(parent, base) : parent;
400
- stats = cacheOf(fs).get(target)?.inode ?? fs.statSync(target);
420
+ stats = cacheOf(fs).statSync(target);
401
421
  }
402
422
  catch (e) {
403
423
  setUVMessage(Object.assign(e, extra));
@@ -406,7 +426,65 @@ export function stat(path, lstat) {
406
426
  }
407
427
  if (!stats)
408
428
  throw UV('ENOENT', extra);
409
- if (checkAccess && !hasAccess(this, stats, constants.R_OK))
410
- throw UV('EACCES', extra);
411
429
  return stats;
412
430
  }
431
+ /**
432
+ * Opens a handle for changing metadata, which does not need read or write access to the file.
433
+ */
434
+ function _openMetadata($, path, syscall, noFollow) {
435
+ const { fullPath, cache, path: resolved, stats } = resolve($, path, noFollow, { syscall });
436
+ if (!stats)
437
+ throw UV('ENOENT', syscall, path);
438
+ return new Handle($, fullPath, resolved, constants.O_RDONLY, cache.ref(resolved, stats));
439
+ }
440
+ export function chmod(path, mode, noFollow) {
441
+ const env_6 = { stack: [], error: void 0, hasError: false };
442
+ try {
443
+ path = normalizePath.call(this, path);
444
+ const numMode = normalizeMode(mode, -1);
445
+ if (numMode < 0)
446
+ throw UV('EINVAL', 'chmod', path);
447
+ const handle = __addDisposableResource(env_6, _openMetadata(this, path, 'chmod', noFollow), false);
448
+ handle.chmodSync(numMode);
449
+ emitChange(this, 'change', handle.path);
450
+ }
451
+ catch (e_6) {
452
+ env_6.error = e_6;
453
+ env_6.hasError = true;
454
+ }
455
+ finally {
456
+ __disposeResources(env_6);
457
+ }
458
+ }
459
+ export function chown(path, uid, gid, noFollow) {
460
+ const env_7 = { stack: [], error: void 0, hasError: false };
461
+ try {
462
+ path = normalizePath.call(this, path);
463
+ const handle = __addDisposableResource(env_7, _openMetadata(this, path, 'chown', noFollow), false);
464
+ handle.chownSync(uid, gid);
465
+ emitChange(this, 'change', handle.path);
466
+ }
467
+ catch (e_7) {
468
+ env_7.error = e_7;
469
+ env_7.hasError = true;
470
+ }
471
+ finally {
472
+ __disposeResources(env_7);
473
+ }
474
+ }
475
+ export function utimes(path, atime, mtime, noFollow) {
476
+ const env_8 = { stack: [], error: void 0, hasError: false };
477
+ try {
478
+ path = normalizePath.call(this, path);
479
+ const handle = __addDisposableResource(env_8, _openMetadata(this, path, 'utimes', noFollow), false);
480
+ handle.utimesSync(normalizeTime(atime), normalizeTime(mtime));
481
+ emitChange(this, 'change', handle.path);
482
+ }
483
+ catch (e_8) {
484
+ env_8.error = e_8;
485
+ env_8.hasError = true;
486
+ }
487
+ finally {
488
+ __disposeResources(env_8);
489
+ }
490
+ }
@@ -1,6 +1,8 @@
1
1
  import type { LockMode, LockRelease } from 'kerium/locks';
2
+ import { type ExceptionExtra } from 'kerium';
2
3
  import type { FileSystem } from '../internal/filesystem.js';
3
4
  import type { InodeLike } from '../internal/inode.js';
5
+ import '../polyfills.js';
4
6
  import { VNode } from './vnode.js';
5
7
  /**
6
8
  * The VFS-level cache of vnodes for a single file system. Modeled after Linux's dcache/icache.
@@ -35,6 +37,18 @@ export declare class VCache {
35
37
  sync(): Promise<void>;
36
38
  /** Sync all dirty vnodes to the backend synchronously */
37
39
  syncSync(): void;
40
+ /** Get the inode for a path, preferring the cached vnode since it may have unsynced changes */
41
+ stat(path: string, extra?: ExceptionExtra): Promise<InodeLike>;
42
+ /** Synchronous version of {@link stat} */
43
+ statSync(path: string, extra?: ExceptionExtra): InodeLike;
44
+ /**
45
+ * Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
46
+ * The returned release function unlocks and unrefs the vnode.
47
+ * If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
48
+ */
49
+ lock(path: string, mode: LockMode, inode?: InodeLike): Promise<LockRelease>;
50
+ /** Synchronous version of {@link lock} */
51
+ lockSync(path: string, mode: LockMode, inode?: InodeLike): LockRelease;
38
52
  }
39
53
  /**
40
54
  * All vnode caches, keyed by file system UUID.
@@ -50,17 +64,3 @@ export declare const caches: Map<`${string}-${string}-${string}-${string}-${stri
50
64
  * @internal
51
65
  */
52
66
  export declare function cacheOf(fs: FileSystem): VCache;
53
- /**
54
- * Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
55
- * The returned release function unlocks and unrefs the vnode.
56
- * If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
57
- * @category VFS
58
- * @internal
59
- */
60
- export declare function lockPath(fs: FileSystem, path: string, mode: LockMode, inode?: InodeLike): Promise<LockRelease>;
61
- /**
62
- * Synchronous version of `lockPath`.
63
- * @category VFS
64
- * @internal
65
- */
66
- export declare function lockPathSync(fs: FileSystem, path: string, mode: LockMode, inode?: InodeLike): LockRelease;
@@ -50,7 +50,10 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
50
50
  var e = new Error(message);
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
+ import { setUVMessage } from 'kerium';
53
54
  import { err } from 'kerium/log';
55
+ import { withoutExceptionContext } from '../internal/error.js';
56
+ import '../polyfills.js';
54
57
  import { VNode } from './vnode.js';
55
58
  /**
56
59
  * The VFS-level cache of vnodes for a single file system. Modeled after Linux's dcache/icache.
@@ -176,6 +179,67 @@ export class VCache {
176
179
  }
177
180
  }
178
181
  }
182
+ /** Get the inode for a path, preferring the cached vnode since it may have unsynced changes */
183
+ async stat(path, extra) {
184
+ try {
185
+ return this.get(path)?.inode ?? (await this.fs.stat(path));
186
+ }
187
+ catch (e) {
188
+ throw extra ? setUVMessage(Object.assign(e, extra)) : e;
189
+ }
190
+ }
191
+ /** Synchronous version of {@link stat} */
192
+ statSync(path, extra) {
193
+ try {
194
+ return this.get(path)?.inode ?? this.fs.statSync(path);
195
+ }
196
+ catch (e) {
197
+ throw extra ? setUVMessage(Object.assign(e, extra)) : e;
198
+ }
199
+ }
200
+ /**
201
+ * Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
202
+ * The returned release function unlocks and unrefs the vnode.
203
+ * If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
204
+ */
205
+ async lock(path, mode, inode) {
206
+ inode ??= await this.stat(path);
207
+ const node = this.ref(path, inode);
208
+ const unlock = await node.lock(mode);
209
+ let released = false;
210
+ const release = () => {
211
+ if (released)
212
+ return;
213
+ released = true;
214
+ unlock();
215
+ this.unref(node);
216
+ };
217
+ release[Symbol.dispose] = release;
218
+ return release;
219
+ }
220
+ /** Synchronous version of {@link lock} */
221
+ lockSync(path, mode, inode) {
222
+ inode ??= this.statSync(path);
223
+ const node = this.ref(path, inode);
224
+ let unlock;
225
+ try {
226
+ unlock = node.lockSync(mode);
227
+ }
228
+ catch (e) {
229
+ this.unref(node);
230
+ throw e;
231
+ }
232
+ let released = false;
233
+ const release = () => {
234
+ if (released)
235
+ return;
236
+ released = true;
237
+ unlock();
238
+ this.unref(node);
239
+ };
240
+ release[Symbol.dispose] = release;
241
+ return release;
242
+ }
179
243
  }
180
244
  /**
181
245
  * All vnode caches, keyed by file system UUID.
@@ -191,61 +255,5 @@ export const caches = new Map();
191
255
  * @internal
192
256
  */
193
257
  export function cacheOf(fs) {
194
- let cache = caches.get(fs.uuid);
195
- if (!cache) {
196
- cache = new VCache(fs);
197
- caches.set(fs.uuid, cache);
198
- }
199
- return cache;
200
- }
201
- /**
202
- * Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
203
- * The returned release function unlocks and unrefs the vnode.
204
- * If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
205
- * @category VFS
206
- * @internal
207
- */
208
- export async function lockPath(fs, path, mode, inode) {
209
- const cache = cacheOf(fs);
210
- inode ??= cache.get(path)?.inode ?? (await fs.stat(path));
211
- const node = cache.ref(path, inode);
212
- const unlock = await node.lock(mode);
213
- let released = false;
214
- const release = () => {
215
- if (released)
216
- return;
217
- released = true;
218
- unlock();
219
- cache.unref(node);
220
- };
221
- release[Symbol.dispose] = release;
222
- return release;
223
- }
224
- /**
225
- * Synchronous version of `lockPath`.
226
- * @category VFS
227
- * @internal
228
- */
229
- export function lockPathSync(fs, path, mode, inode) {
230
- const cache = cacheOf(fs);
231
- inode ??= cache.get(path)?.inode ?? fs.statSync(path);
232
- const node = cache.ref(path, inode);
233
- let unlock;
234
- try {
235
- unlock = node.lockSync(mode);
236
- }
237
- catch (e) {
238
- cache.unref(node);
239
- throw e;
240
- }
241
- let released = false;
242
- const release = () => {
243
- if (released)
244
- return;
245
- released = true;
246
- unlock();
247
- cache.unref(node);
248
- };
249
- release[Symbol.dispose] = release;
250
- return release;
258
+ return caches.getOrInsertComputed(fs.uuid, () => new VCache(withoutExceptionContext(fs)));
251
259
  }
@@ -3,6 +3,7 @@ import type * as fs from 'node:fs';
3
3
  import type { V_Context } from '../context.js';
4
4
  import { EventEmitter } from 'eventemitter3';
5
5
  import { type Stats } from '../node/stats.js';
6
+ import '../polyfills.js';
6
7
  /**
7
8
  * Base class for file system watchers.
8
9
  * Provides event handling capabilities for watching file system changes.
@@ -4,6 +4,7 @@ import { contextOf } from '../internal/contexts.js';
4
4
  import { isStatsEqual } from '../node/stats.js';
5
5
  import { statSync } from '../node/sync.js';
6
6
  import { basename, dirname, join, relative } from '../path.js';
7
+ import '../polyfills.js';
7
8
  import { normalizePath } from '../utils.js';
8
9
  /** Alias so `.call` resolves to the non-bigint overload */
9
10
  const _statSync = statSync;
@@ -130,10 +131,7 @@ export class StatWatcher extends Watcher {
130
131
  const watchers = new Map();
131
132
  export function addWatcher(path, watcher) {
132
133
  const normalizedPath = normalizePath(path);
133
- if (!watchers.has(normalizedPath)) {
134
- watchers.set(normalizedPath, new Set());
135
- }
136
- watchers.get(normalizedPath).add(watcher);
134
+ watchers.getOrInsertComputed(normalizedPath, () => new Set()).add(watcher);
137
135
  }
138
136
  export function removeWatcher(path, watcher) {
139
137
  const normalizedPath = normalizePath(path);