@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/async.js CHANGED
@@ -53,16 +53,16 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
53
53
  import { rethrow, 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';
57
- import { hasAccess, isDirectory, isSymbolicLink } from '../internal/inode.js';
56
+ import { applyUmask, contextOf } from '../internal/contexts.js';
57
+ import { assertRemovable, assertSearchable, hasAccess, isDirectory, isSymbolicLink } from '../internal/inode.js';
58
58
  import { basename, dirname, join, parse, resolve as resolvePath } from '../path.js';
59
- import { normalizeMode, normalizePath } from '../utils.js';
60
- import { checkAccess } from './config.js';
59
+ import { normalizeMode, normalizePath, normalizeTime } from '../utils.js';
60
+ import { checkAccess, resolveFullWalk } from './config.js';
61
61
  import { Dirent, ifToDt } from './dir.js';
62
62
  import { Handle } from './file.js';
63
63
  import * as flags from './flags.js';
64
64
  import { resolveMount } from './shared.js';
65
- import { cacheOf, lockPath } from './vcache.js';
65
+ import { cacheOf } from './vcache.js';
66
66
  import { emitChange } from './watchers.js';
67
67
  /**
68
68
  * Resolves the mount and real path for a path.
@@ -71,38 +71,44 @@ import { emitChange } from './watchers.js';
71
71
  */
72
72
  export async function resolve($, path, preserveSymlinks, extra) {
73
73
  path = resolvePath.call($, path);
74
- if (preserveSymlinks) {
75
- const resolved = resolveMount(path, $, extra);
76
- const stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? (await resolved.fs.stat(resolved.path).catch(() => undefined));
77
- return { ...resolved, fullPath: path, stats };
78
- }
79
74
  /* Try to resolve it directly. If this works,
80
75
  that means we don't need to perform any resolution for parent directories. */
81
- try {
82
- const resolved = resolveMount(path, $);
83
- const stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? (await resolved.fs.stat(resolved.path));
84
- if (!isSymbolicLink(stats)) {
85
- return { ...resolved, fullPath: path, stats };
76
+ if (!resolveFullWalk)
77
+ try {
78
+ const resolved = resolveMount(path, $);
79
+ const stats = await resolved.cache.stat(resolved.path);
80
+ if (!isSymbolicLink(stats) || preserveSymlinks) {
81
+ return { ...resolved, fullPath: path, stats };
82
+ }
83
+ const target = resolvePath.call($, dirname(path), await readlink.call($, path));
84
+ return await resolve($, target, preserveSymlinks, extra);
85
+ }
86
+ catch {
87
+ // Go the long way
86
88
  }
87
- const target = resolvePath.call($, dirname(path), await readlink.call($, path));
88
- return await resolve($, target, preserveSymlinks, extra);
89
+ const { base, dir } = parse(path);
90
+ const $ex = { syscall: 'stat', path, ...extra };
91
+ let realDir = '/';
92
+ if (dir != '/') {
93
+ const parent = await resolve($, dir, false, extra);
94
+ if (parent.stats && isDirectory(parent.stats))
95
+ assertSearchable($, parent.stats, $ex);
96
+ realDir = parent.fullPath;
89
97
  }
90
- catch {
91
- // Go the long way
98
+ else if (base && checkAccess) {
99
+ const root = resolveMount('/', $);
100
+ assertSearchable($, await root.cache.stat(root.path), $ex);
92
101
  }
93
- const { base, dir } = parse(path);
94
- const realDir = dir == '/' ? '/' : (await resolve($, dir, false, extra)).fullPath;
95
102
  const maybePath = join(realDir, base);
96
103
  const resolved = resolveMount(maybePath, $);
97
- const stats = cacheOf(resolved.fs).get(resolved.path)?.inode
98
- ?? (await resolved.fs.stat(resolved.path).catch((e) => {
99
- if (e.code == 'ENOENT')
100
- return;
101
- throw setUVMessage(Object.assign(e, { syscall: 'stat', path: maybePath, ...extra }));
102
- }));
104
+ const stats = await resolved.cache.stat(resolved.path).catch((e) => {
105
+ if (e.code == 'ENOENT')
106
+ return;
107
+ throw setUVMessage(Object.assign(e, { syscall: 'stat', path: maybePath, ...extra }));
108
+ });
103
109
  if (!stats)
104
110
  return { ...resolved, fullPath: maybePath };
105
- if (!isSymbolicLink(stats)) {
111
+ if (!isSymbolicLink(stats) || preserveSymlinks) {
106
112
  return { ...resolved, fullPath: maybePath, stats };
107
113
  }
108
114
  const target = resolvePath.call($, realDir, await readlink.call($, maybePath));
@@ -117,7 +123,7 @@ export async function open($, path, opt) {
117
123
  const mode = normalizeMode(opt.mode, 0o644), flag = flags.parse(opt.flag);
118
124
  const $ex = { syscall: 'open', path };
119
125
  // eslint-disable-next-line prefer-const
120
- let { fs, path: resolved, stats } = await resolve($, path, opt.preserveSymlinks, $ex);
126
+ let { fs, cache, path: resolved, stats } = await resolve($, path, opt.preserveSymlinks, $ex);
121
127
  if (!stats) {
122
128
  const env_1 = { stack: [], error: void 0, hasError: false };
123
129
  try {
@@ -125,25 +131,25 @@ export async function open($, path, opt) {
125
131
  throw UV('ENOENT', $ex);
126
132
  // Create the file
127
133
  const parentPath = dirname(resolved);
128
- const parentStats = await fs.stat(parentPath);
129
- if (checkAccess && !hasAccess($, parentStats, constants.W_OK))
134
+ const parentStats = await cache.stat(parentPath, $ex);
135
+ if (checkAccess && !hasAccess($, parentStats, constants.W_OK | constants.X_OK))
130
136
  throw UV('EACCES', 'open', dirname(path));
131
137
  if (!isDirectory(parentStats))
132
138
  throw UV('ENOTDIR', 'open', dirname(path));
133
139
  if (!opt.allowDirectory && isDirectory({ mode }))
134
140
  throw UV('EISDIR', 'open', path);
135
141
  // Serialize entry creation with other operations on the parent directory
136
- const _ = __addDisposableResource(env_1, await lockPath(fs, parentPath, 'rw', parentStats), false);
137
- stats = cacheOf(fs).get(resolved)?.inode ?? (await fs.stat(resolved).catch(() => undefined));
142
+ const _ = __addDisposableResource(env_1, await cache.lock(parentPath, 'rw', parentStats), false);
143
+ stats = await cache.stat(resolved).catch(() => undefined);
138
144
  if (!stats) {
139
145
  const { euid: uid, egid: gid } = contextOf($).credentials;
140
146
  const inode = await fs.createFile(resolved, {
141
- mode,
147
+ mode: applyUmask($, mode),
142
148
  uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
143
149
  gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
144
150
  });
145
151
  emitChange($, 'rename', path);
146
- return new Handle($, path, resolved, flag, cacheOf(fs).ref(resolved, inode));
152
+ return new Handle($, path, resolved, flag, cache.ref(resolved, inode));
147
153
  }
148
154
  }
149
155
  catch (e_1) {
@@ -158,9 +164,9 @@ export async function open($, path, opt) {
158
164
  throw UV('EACCES', $ex);
159
165
  if (flag & constants.O_EXCL)
160
166
  throw UV('EEXIST', $ex);
161
- if (!opt.allowDirectory && isDirectory({ mode }))
167
+ if (!opt.allowDirectory && isDirectory(stats))
162
168
  throw UV('EISDIR', 'open', path);
163
- const handle = new Handle($, path, resolved, flag, cacheOf(fs).ref(resolved, stats));
169
+ const handle = new Handle($, path, resolved, flag, cache.ref(resolved, stats));
164
170
  if (flag & constants.O_TRUNC)
165
171
  await handle.truncate(0);
166
172
  return handle;
@@ -183,20 +189,21 @@ export async function readlink(path) {
183
189
  export async function mkdir(path, options = {}) {
184
190
  const original = normalizePath.call(this, path);
185
191
  const { euid: uid, egid: gid } = contextOf(this).credentials;
186
- const { mode = 0o777, recursive } = options;
192
+ const { recursive } = options;
193
+ const mode = applyUmask(this, options.mode ?? 0o777);
187
194
  const $ex = { syscall: 'mkdir', path: original };
188
195
  const { fullPath: realParent } = await resolve(this, dirname(original), false, $ex);
189
- const { fs, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
196
+ const { fs, cache, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
190
197
  const followed = realParent != dirname(original);
191
198
  let firstCreated;
192
199
  const __create = async (path, resolved) => {
193
200
  const env_2 = { stack: [], error: void 0, hasError: false };
194
201
  try {
195
202
  const parentPath = dirname(resolved);
196
- const parent = recursive && parentPath != '/' ? await __create(dirname(path), parentPath) : await fs.stat(parentPath);
197
- const _ = __addDisposableResource(env_2, await lockPath(fs, parentPath, 'rw', parent), false);
203
+ const parent = recursive && parentPath != '/' ? await __create(dirname(path), parentPath) : await cache.stat(parentPath, $ex);
204
+ const _ = __addDisposableResource(env_2, await cache.lock(parentPath, 'rw', parent), false);
198
205
  if (recursive) {
199
- const existing = cacheOf(fs).get(resolved)?.inode ?? (await fs.stat(resolved).catch(() => null));
206
+ const existing = await cache.stat(resolved).catch(() => null);
200
207
  if (existing) {
201
208
  const stats = isSymbolicLink(existing) ? (await resolve(this, path, false, $ex)).stats : existing;
202
209
  if (!stats)
@@ -209,7 +216,7 @@ export async function mkdir(path, options = {}) {
209
216
  if (followed && (await fs.exists(join(parentPath, basename(path)))))
210
217
  throw UV('ENOTDIR', 'mkdir', path);
211
218
  }
212
- if (checkAccess && !hasAccess(this, parent, constants.W_OK))
219
+ if (checkAccess && !hasAccess(this, parent, constants.W_OK | constants.X_OK))
213
220
  throw UV('EACCES', 'mkdir', path);
214
221
  const inode = await fs
215
222
  .mkdir(resolved, {
@@ -237,7 +244,7 @@ export async function mkdir(path, options = {}) {
237
244
  export async function readdir(path, options = {}) {
238
245
  path = normalizePath.call(this, path);
239
246
  const $ex = { syscall: 'scandir', path };
240
- const { fs, path: resolved, stats } = await resolve(this, path, false, $ex);
247
+ const { fs, cache, path: resolved, stats } = await resolve(this, path, false, $ex);
241
248
  if (!stats)
242
249
  throw UV('ENOENT', $ex);
243
250
  if (checkAccess && !hasAccess(this, stats, constants.R_OK))
@@ -248,7 +255,7 @@ export async function readdir(path, options = {}) {
248
255
  {
249
256
  const env_3 = { stack: [], error: void 0, hasError: false };
250
257
  try {
251
- const _ = __addDisposableResource(env_3, await lockPath(fs, resolved, 'ro', stats), false);
258
+ const _ = __addDisposableResource(env_3, await cache.lock(resolved, 'ro', stats), false);
252
259
  entries = await fs.readdir(resolved);
253
260
  }
254
261
  catch (e_3) {
@@ -261,7 +268,7 @@ export async function readdir(path, options = {}) {
261
268
  }
262
269
  const values = [];
263
270
  const addEntry = async (entry) => {
264
- const entryStats = await fs.stat(join(resolved, entry)).catch((e) => {
271
+ const entryStats = await cache.stat(join(resolved, entry), $ex).catch((e) => {
265
272
  if (e.code == 'ENOENT')
266
273
  return;
267
274
  throw e;
@@ -290,7 +297,7 @@ export async function rename(oldPath, newPath) {
290
297
  newPath = normalizePath.call(this, newPath);
291
298
  const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
292
299
  const src = await resolve(this, oldPath, true, $ex);
293
- const dst = resolveMount(newPath, this, $ex);
300
+ const dst = await resolve(this, newPath, true, $ex);
294
301
  if (src.fs.uuid !== dst.fs.uuid)
295
302
  throw UV('EXDEV', $ex);
296
303
  // A directory can not be moved inside itself
@@ -298,22 +305,26 @@ export async function rename(oldPath, newPath) {
298
305
  throw UV('EINVAL', $ex);
299
306
  if (!src.stats)
300
307
  throw UV('ENOENT', $ex);
301
- const fs = src.fs;
302
- const oldParent = await fs.stat(dirname(src.path));
303
- const newParent = await fs.stat(dirname(dst.path));
304
- const newStats = await fs.stat(dst.path).catch((e) => {
308
+ const srcDir = dirname(src.path);
309
+ const dstDir = dirname(dst.path);
310
+ const oldParent = await src.cache.stat(srcDir, $ex);
311
+ const newParent = await src.cache.stat(dstDir, $ex);
312
+ const newStats = await src.cache.stat(dst.path, $ex).catch((e) => {
305
313
  if (e.code == 'ENOENT')
306
314
  return null;
307
315
  throw e;
308
316
  });
309
- if (checkAccess && (!hasAccess(this, oldParent, constants.R_OK) || !hasAccess(this, newParent, constants.W_OK)))
317
+ assertRemovable(this, oldParent, src.stats, $ex);
318
+ if (newStats)
319
+ assertRemovable(this, newParent, newStats, $ex);
320
+ if (checkAccess && !hasAccess(this, newParent, constants.W_OK | constants.X_OK))
321
+ throw UV('EACCES', $ex);
322
+ if (checkAccess && isDirectory(src.stats) && srcDir != dstDir && !hasAccess(this, src.stats, constants.W_OK))
310
323
  throw UV('EACCES', $ex);
311
324
  if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
312
325
  throw UV('EISDIR', $ex);
313
326
  if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
314
327
  throw UV('ENOTDIR', $ex);
315
- const srcDir = dirname(src.path);
316
- const dstDir = dirname(dst.path);
317
328
  // Lock both parent directories, ordered by inode number to avoid ABBA deadlocks (like Linux's `lock_two_nondirectories`)
318
329
  const parents = [
319
330
  [srcDir, oldParent],
@@ -321,10 +332,10 @@ export async function rename(oldPath, newPath) {
321
332
  ];
322
333
  if (oldParent.ino > newParent.ino)
323
334
  parents.reverse();
324
- const _first = __addDisposableResource(env_4, await lockPath(fs, parents[0][0], 'rw', parents[0][1]), false);
325
- const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : await lockPath(fs, parents[1][0], 'rw', parents[1][1]), false);
335
+ const _first = __addDisposableResource(env_4, await src.cache.lock(parents[0][0], 'rw', parents[0][1]), false);
336
+ const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : await src.cache.lock(parents[1][0], 'rw', parents[1][1]), false);
326
337
  await src.fs.rename(src.path, dst.path);
327
- cacheOf(fs).rename(src.path, dst.path);
338
+ src.cache.rename(src.path, dst.path);
328
339
  emitChange(this, 'rename', oldPath);
329
340
  emitChange(this, 'rename', newPath);
330
341
  }
@@ -342,24 +353,24 @@ export async function link(target, link) {
342
353
  target = normalizePath.call(this, target);
343
354
  link = normalizePath.call(this, link);
344
355
  const $ex = { syscall: 'link', path: target, dest: link };
345
- const { fs, path: resolved } = resolveMount(target, this, $ex);
346
- const dst = resolveMount(link, this, $ex);
356
+ const { fs, cache, path: resolved } = await resolve(this, target, true, $ex);
357
+ const dst = await resolve(this, link, true, $ex);
347
358
  if (fs.uuid != dst.fs.uuid)
348
359
  throw UV('EXDEV', $ex);
349
- const stats = await fs.stat(resolved);
360
+ const stats = await cache.stat(resolved, $ex);
350
361
  if (checkAccess) {
351
362
  if (!hasAccess(this, stats, constants.R_OK))
352
363
  throw UV('EACCES', $ex);
353
- const dirStats = await fs.stat(dirname(resolved));
364
+ const dirStats = await cache.stat(dirname(resolved), $ex);
354
365
  if (!hasAccess(this, dirStats, constants.R_OK))
355
366
  throw UV('EACCES', $ex);
356
- const destStats = await fs.stat(dirname(dst.path));
367
+ const destStats = await cache.stat(dirname(dst.path), $ex);
357
368
  if (!hasAccess(this, destStats, constants.W_OK))
358
369
  throw UV('EACCES', $ex);
359
370
  }
360
- const _ = __addDisposableResource(env_5, await lockPath(fs, dirname(dst.path), 'rw'), false);
371
+ const _ = __addDisposableResource(env_5, await cache.lock(dirname(dst.path), 'rw'), false);
361
372
  await fs.link(resolved, dst.path);
362
- cacheOf(fs).link(resolved, dst.path);
373
+ cache.link(resolved, dst.path);
363
374
  }
364
375
  catch (e_5) {
365
376
  env_5.error = e_5;
@@ -377,13 +388,82 @@ export async function stat(path, lstat) {
377
388
  stats = (await resolve(this, path, false, extra)).stats;
378
389
  else {
379
390
  const { base, dir } = parse(path);
380
- const { fs, path: parent } = await resolve(this, dir, false, extra);
381
- const target = base ? join(parent, base) : parent;
382
- stats = cacheOf(fs).get(target)?.inode ?? (await fs.stat(target).catch(rethrow(extra)));
391
+ const parent = await resolve(this, dir, false, extra);
392
+ if (parent.stats && isDirectory(parent.stats))
393
+ assertSearchable(this, parent.stats, extra);
394
+ const { root, mounts } = contextOf(this);
395
+ const mounted = base && mounts.get(join(root, parent.fullPath, base));
396
+ const fs = mounted || parent.fs;
397
+ const target = mounted ? '/' : join(parent.path, base);
398
+ stats = await cacheOf(fs).stat(target).catch(rethrow(extra));
383
399
  }
384
400
  if (!stats)
385
401
  throw UV('ENOENT', extra);
386
- if (checkAccess && !hasAccess(this, stats, constants.R_OK))
387
- throw UV('EACCES', extra);
388
402
  return stats;
389
403
  }
404
+ /**
405
+ * Opens a handle for changing metadata, which does not need read or write access to the file.
406
+ */
407
+ async function _openMetadata($, path, syscall, noFollow) {
408
+ const { fullPath, cache, path: resolved, stats } = await resolve($, path, noFollow, { syscall });
409
+ if (!stats)
410
+ throw UV('ENOENT', syscall, path);
411
+ return new Handle($, fullPath, resolved, constants.O_RDONLY, cache.ref(resolved, stats));
412
+ }
413
+ export async function chmod(path, mode, noFollow) {
414
+ const env_6 = { stack: [], error: void 0, hasError: false };
415
+ try {
416
+ path = normalizePath.call(this, path);
417
+ const numMode = normalizeMode(mode, -1);
418
+ if (numMode < 0)
419
+ throw UV('EINVAL', 'chmod', path);
420
+ const handle = __addDisposableResource(env_6, await _openMetadata(this, path, 'chmod', noFollow), true);
421
+ await handle.chmod(numMode);
422
+ emitChange(this, 'change', handle.path);
423
+ }
424
+ catch (e_6) {
425
+ env_6.error = e_6;
426
+ env_6.hasError = true;
427
+ }
428
+ finally {
429
+ const result_1 = __disposeResources(env_6);
430
+ if (result_1)
431
+ await result_1;
432
+ }
433
+ }
434
+ export async function chown(path, uid, gid, noFollow) {
435
+ const env_7 = { stack: [], error: void 0, hasError: false };
436
+ try {
437
+ path = normalizePath.call(this, path);
438
+ const handle = __addDisposableResource(env_7, await _openMetadata(this, path, 'chown', noFollow), true);
439
+ await handle.chown(uid, gid);
440
+ emitChange(this, 'change', handle.path);
441
+ }
442
+ catch (e_7) {
443
+ env_7.error = e_7;
444
+ env_7.hasError = true;
445
+ }
446
+ finally {
447
+ const result_2 = __disposeResources(env_7);
448
+ if (result_2)
449
+ await result_2;
450
+ }
451
+ }
452
+ export async function utimes(path, atime, mtime, noFollow) {
453
+ const env_8 = { stack: [], error: void 0, hasError: false };
454
+ try {
455
+ path = normalizePath.call(this, path);
456
+ const handle = __addDisposableResource(env_8, await _openMetadata(this, path, 'utimes', noFollow), true);
457
+ await handle.utimes(normalizeTime(atime), normalizeTime(mtime));
458
+ emitChange(this, 'change', handle.path);
459
+ }
460
+ catch (e_8) {
461
+ env_8.error = e_8;
462
+ env_8.hasError = true;
463
+ }
464
+ finally {
465
+ const result_3 = __disposeResources(env_8);
466
+ if (result_3)
467
+ await result_3;
468
+ }
469
+ }
@@ -1,6 +1,15 @@
1
1
  /** Whether to perform access checks */
2
2
  export declare let checkAccess: boolean;
3
+ /** If set, path resolution will always do a full walk. More correct but potentially very slow. */
4
+ export declare let resolveFullWalk: boolean;
3
5
  /**
4
6
  * @internal @hidden
5
7
  */
6
- export declare function _setAccessChecks(value: boolean): void;
8
+ export interface _VFSConfig {
9
+ checkAccess: boolean;
10
+ resolveFullWalk: boolean;
11
+ }
12
+ /**
13
+ * @internal @hidden
14
+ */
15
+ export declare function _setVFSConfig(cfg: _VFSConfig): void;
@@ -1,9 +1,12 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
2
  /** Whether to perform access checks */
3
3
  export let checkAccess = true;
4
+ /** If set, path resolution will always do a full walk. More correct but potentially very slow. */
5
+ export let resolveFullWalk = false;
4
6
  /**
5
7
  * @internal @hidden
6
8
  */
7
- export function _setAccessChecks(value) {
8
- checkAccess = value;
9
+ export function _setVFSConfig(cfg) {
10
+ checkAccess = cfg.checkAccess;
11
+ resolveFullWalk = cfg.resolveFullWalk;
9
12
  }
package/dist/vfs/file.js CHANGED
@@ -89,7 +89,7 @@ import { UV, withErrno } from 'kerium';
89
89
  import { memoize } from 'utilium';
90
90
  import * as c from '../constants.js';
91
91
  import { contextOf } from '../internal/contexts.js';
92
- import { _chown, InodeFlags, isBlockDevice, isCharacterDevice } from '../internal/inode.js';
92
+ import { _chmod, _chown, assertCanChown, assertOwner, InodeFlags, isBlockDevice, isCharacterDevice } from '../internal/inode.js';
93
93
  import '../polyfills.js';
94
94
  import { validateFD } from '../utils.js';
95
95
  import { cacheOf } from './vcache.js';
@@ -328,9 +328,10 @@ let Handle = (() => {
328
328
  try {
329
329
  if (this.closed)
330
330
  throw UV('EBADF', 'chmod', this.path);
331
+ assertOwner(this.context, this.inode, { syscall: 'chmod', path: this.path });
331
332
  const _ = __addDisposableResource(env_5, this.vnode.lockSync('rw'), false);
332
333
  this.vnode.metadataDirty = true;
333
- this.inode.mode = (this.inode.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
334
+ _chmod(this.context, this.inode, mode);
334
335
  if (this._isSync || mode > c.S_IFMT)
335
336
  this.vnode.syncSync();
336
337
  }
@@ -346,7 +347,8 @@ let Handle = (() => {
346
347
  const env_6 = { stack: [], error: void 0, hasError: false };
347
348
  try {
348
349
  if (this.closed)
349
- throw UV('EBADF', 'chmod', this.path);
350
+ throw UV('EBADF', 'chown', this.path);
351
+ assertCanChown(this.context, this.inode, uid, gid, { syscall: 'chown', path: this.path });
350
352
  const _ = __addDisposableResource(env_6, this.vnode.lockSync('rw'), false);
351
353
  this.vnode.metadataDirty = true;
352
354
  _chown(this.inode, uid, gid);
@@ -369,6 +371,7 @@ let Handle = (() => {
369
371
  try {
370
372
  if (this.closed)
371
373
  throw UV('EBADF', 'utimes', this.path);
374
+ assertOwner(this.context, this.inode, { syscall: 'utimes', path: this.path });
372
375
  const _ = __addDisposableResource(env_7, this.vnode.lockSync('rw'), false);
373
376
  this.vnode.metadataDirty = true;
374
377
  this.inode.atimeMs = atime;
@@ -542,9 +545,10 @@ let Handle = (() => {
542
545
  try {
543
546
  if (this.closed)
544
547
  throw UV('EBADF', 'chmod', this.path);
548
+ assertOwner(this.context, this.inode, { syscall: 'chmod', path: this.path });
545
549
  const _ = __addDisposableResource(env_12, await this.vnode.lock('rw'), false);
546
550
  this.vnode.metadataDirty = true;
547
- this.inode.mode = (this.inode.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
551
+ _chmod(this.context, this.inode, mode);
548
552
  if (this._isSync || mode > c.S_IFMT)
549
553
  await this.vnode.sync();
550
554
  }
@@ -561,6 +565,7 @@ let Handle = (() => {
561
565
  try {
562
566
  if (this.closed)
563
567
  throw UV('EBADF', 'chown', this.path);
568
+ assertCanChown(this.context, this.inode, uid, gid, { syscall: 'chown', path: this.path });
564
569
  const _ = __addDisposableResource(env_13, await this.vnode.lock('rw'), false);
565
570
  this.vnode.metadataDirty = true;
566
571
  _chown(this.inode, uid, gid);
@@ -583,6 +588,7 @@ let Handle = (() => {
583
588
  try {
584
589
  if (this.closed)
585
590
  throw UV('EBADF', 'utimes', this.path);
591
+ assertOwner(this.context, this.inode, { syscall: 'utimes', path: this.path });
586
592
  const _ = __addDisposableResource(env_14, await this.vnode.lock('rw'), false);
587
593
  this.vnode.metadataDirty = true;
588
594
  this.inode.atimeMs = atime;
package/dist/vfs/ioctl.js CHANGED
@@ -30,7 +30,7 @@ export async function ioctl(path, command, ...args) {
30
30
  const mnt = await resolveAsync(this, path, false, { syscall: 'ioctl', path });
31
31
  if (!mnt.stats)
32
32
  throw UV('ENOENT', { syscall: 'ioctl', path });
33
- vcache = cacheOf(mnt.fs);
33
+ vcache = mnt.cache;
34
34
  vnode = vcache.ref(mnt.path, vcache.get(mnt.path)?.inode ?? mnt.stats);
35
35
  }
36
36
  const context = {
@@ -68,7 +68,7 @@ export function ioctlSync(path, command, ...args) {
68
68
  const mnt = resolveSync(this, path, false, { syscall: 'ioctl', path });
69
69
  if (!mnt.stats)
70
70
  throw UV('ENOENT', { syscall: 'ioctl', path });
71
- vcache = cacheOf(mnt.fs);
71
+ vcache = mnt.cache;
72
72
  vnode = vcache.ref(mnt.path, vcache.get(mnt.path)?.inode ?? mnt.stats);
73
73
  }
74
74
  const context = {
@@ -4,6 +4,7 @@ import type { FileSystem } from '../internal/filesystem.js';
4
4
  import type { InodeLike } from '../internal/inode.js';
5
5
  import { type ExceptionExtra } from 'kerium';
6
6
  import { type AbsolutePath } from '../path.js';
7
+ import { VCache } from './vcache.js';
7
8
  /**
8
9
  * @internal @hidden
9
10
  */
@@ -33,6 +34,7 @@ export declare function umount(this: V_Context, mountPoint: string): void;
33
34
  */
34
35
  export interface ResolvedMount {
35
36
  fs: FileSystem;
37
+ cache: VCache;
36
38
  path: string;
37
39
  mountPoint: string;
38
40
  root: string;
@@ -9,7 +9,7 @@ import { credentialsAllowRoot } from '../internal/credentials.js';
9
9
  import { withExceptionContext } from '../internal/error.js';
10
10
  import { join, resolve } from '../path.js';
11
11
  import { normalizePath } from '../utils.js';
12
- import { caches, VCache } from './vcache.js';
12
+ import { cacheOf, caches, VCache } from './vcache.js';
13
13
  /**
14
14
  * The map of mount points
15
15
  * @category Backends and Configuration
@@ -34,8 +34,7 @@ export function mount(mountPoint, fs) {
34
34
  throw err(withErrno('EINVAL', 'Mount point is already in use: ' + mountPoint));
35
35
  fs._mountPoint = mountPoint;
36
36
  $.mounts.set(mountPoint, fs);
37
- if (!caches.has(fs.uuid))
38
- caches.set(fs.uuid, new VCache(fs));
37
+ caches.getOrInsertComputed(fs.uuid, () => new VCache(fs));
39
38
  info(`Mounted ${fs.name} on ${mountPoint}`);
40
39
  debug(`${fs.name} attributes: ${[...fs.attributes].map(([k, v]) => (v !== undefined && v !== null ? k + '=' + v : k)).join(', ')}`);
41
40
  }
@@ -91,7 +90,7 @@ export function resolveMount(path, ctx, extra) {
91
90
  path = path.toLowerCase();
92
91
  if (case_fold === 'upper')
93
92
  path = path.toUpperCase();
94
- return { fs: withExceptionContext(fs, _exceptionContext), path, mountPoint, root };
93
+ return { fs: withExceptionContext(fs, _exceptionContext), cache: cacheOf(fs), path, mountPoint, root };
95
94
  }
96
95
  throw alert(new Exception(Errno.EIO, 'No file system for ' + path));
97
96
  }
@@ -1,4 +1,4 @@
1
- import type { PathLike } from 'node:fs';
1
+ import type { Mode, PathLike, TimeLike } from 'node:fs';
2
2
  import type { V_Context } from '../context.js';
3
3
  import type { InodeLike } from '../internal/inode.js';
4
4
  import type { MkdirOptions, OpenOptions, ReaddirOptions, ResolvedPath } from './shared.js';
@@ -23,3 +23,6 @@ export declare function readdir(this: V_Context, path: PathLike, options?: Readd
23
23
  export declare function rename(this: V_Context, oldPath: PathLike, newPath: PathLike): void;
24
24
  export declare function link(this: V_Context, target: PathLike, link: PathLike): void;
25
25
  export declare function stat(this: V_Context, path: PathLike, lstat: boolean): InodeLike;
26
+ export declare function chmod(this: V_Context, path: PathLike, mode: Mode, noFollow?: boolean): void;
27
+ export declare function chown(this: V_Context, path: PathLike, uid: number, gid: number, noFollow?: boolean): void;
28
+ export declare function utimes(this: V_Context, path: PathLike, atime: TimeLike, mtime: TimeLike, noFollow?: boolean): void;