@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
package/dist/vfs/async.js CHANGED
@@ -54,15 +54,15 @@ import { rethrow, setUVMessage, UV } from 'kerium';
54
54
  import { decodeUTF8 } from 'utilium';
55
55
  import * as constants from '../constants.js';
56
56
  import { contextOf } from '../internal/contexts.js';
57
- import { hasAccess, isDirectory, isSymbolicLink } from '../internal/inode.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
59
  import { normalizeMode, normalizePath } from '../utils.js';
60
- import { checkAccess } from './config.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);
86
85
  }
87
- const target = resolvePath.call($, dirname(path), await readlink.call($, path));
88
- return await resolve($, target, preserveSymlinks, extra);
86
+ catch {
87
+ // Go the long way
88
+ }
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,16 +131,16 @@ 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, {
@@ -143,7 +149,7 @@ export async function open($, path, opt) {
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) {
@@ -160,7 +166,7 @@ export async function open($, path, opt) {
160
166
  throw UV('EEXIST', $ex);
161
167
  if (!opt.allowDirectory && isDirectory({ mode }))
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;
@@ -186,17 +192,17 @@ export async function mkdir(path, options = {}) {
186
192
  const { mode = 0o777, recursive } = options;
187
193
  const $ex = { syscall: 'mkdir', path: original };
188
194
  const { fullPath: realParent } = await resolve(this, dirname(original), false, $ex);
189
- const { fs, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
195
+ const { fs, cache, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
190
196
  const followed = realParent != dirname(original);
191
197
  let firstCreated;
192
198
  const __create = async (path, resolved) => {
193
199
  const env_2 = { stack: [], error: void 0, hasError: false };
194
200
  try {
195
201
  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);
202
+ const parent = recursive && parentPath != '/' ? await __create(dirname(path), parentPath) : await cache.stat(parentPath, $ex);
203
+ const _ = __addDisposableResource(env_2, await cache.lock(parentPath, 'rw', parent), false);
198
204
  if (recursive) {
199
- const existing = cacheOf(fs).get(resolved)?.inode ?? (await fs.stat(resolved).catch(() => null));
205
+ const existing = await cache.stat(resolved).catch(() => null);
200
206
  if (existing) {
201
207
  const stats = isSymbolicLink(existing) ? (await resolve(this, path, false, $ex)).stats : existing;
202
208
  if (!stats)
@@ -209,7 +215,7 @@ export async function mkdir(path, options = {}) {
209
215
  if (followed && (await fs.exists(join(parentPath, basename(path)))))
210
216
  throw UV('ENOTDIR', 'mkdir', path);
211
217
  }
212
- if (checkAccess && !hasAccess(this, parent, constants.W_OK))
218
+ if (checkAccess && !hasAccess(this, parent, constants.W_OK | constants.X_OK))
213
219
  throw UV('EACCES', 'mkdir', path);
214
220
  const inode = await fs
215
221
  .mkdir(resolved, {
@@ -237,7 +243,7 @@ export async function mkdir(path, options = {}) {
237
243
  export async function readdir(path, options = {}) {
238
244
  path = normalizePath.call(this, path);
239
245
  const $ex = { syscall: 'scandir', path };
240
- const { fs, path: resolved, stats } = await resolve(this, path, false, $ex);
246
+ const { fs, cache, path: resolved, stats } = await resolve(this, path, false, $ex);
241
247
  if (!stats)
242
248
  throw UV('ENOENT', $ex);
243
249
  if (checkAccess && !hasAccess(this, stats, constants.R_OK))
@@ -248,7 +254,7 @@ export async function readdir(path, options = {}) {
248
254
  {
249
255
  const env_3 = { stack: [], error: void 0, hasError: false };
250
256
  try {
251
- const _ = __addDisposableResource(env_3, await lockPath(fs, resolved, 'ro', stats), false);
257
+ const _ = __addDisposableResource(env_3, await cache.lock(resolved, 'ro', stats), false);
252
258
  entries = await fs.readdir(resolved);
253
259
  }
254
260
  catch (e_3) {
@@ -261,7 +267,7 @@ export async function readdir(path, options = {}) {
261
267
  }
262
268
  const values = [];
263
269
  const addEntry = async (entry) => {
264
- const entryStats = await fs.stat(join(resolved, entry)).catch((e) => {
270
+ const entryStats = await cache.stat(join(resolved, entry), $ex).catch((e) => {
265
271
  if (e.code == 'ENOENT')
266
272
  return;
267
273
  throw e;
@@ -290,7 +296,7 @@ export async function rename(oldPath, newPath) {
290
296
  newPath = normalizePath.call(this, newPath);
291
297
  const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
292
298
  const src = await resolve(this, oldPath, true, $ex);
293
- const dst = resolveMount(newPath, this, $ex);
299
+ const dst = await resolve(this, newPath, true, $ex);
294
300
  if (src.fs.uuid !== dst.fs.uuid)
295
301
  throw UV('EXDEV', $ex);
296
302
  // A directory can not be moved inside itself
@@ -298,22 +304,26 @@ export async function rename(oldPath, newPath) {
298
304
  throw UV('EINVAL', $ex);
299
305
  if (!src.stats)
300
306
  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) => {
307
+ const srcDir = dirname(src.path);
308
+ const dstDir = dirname(dst.path);
309
+ const oldParent = await src.cache.stat(srcDir, $ex);
310
+ const newParent = await src.cache.stat(dstDir, $ex);
311
+ const newStats = await src.cache.stat(dst.path, $ex).catch((e) => {
305
312
  if (e.code == 'ENOENT')
306
313
  return null;
307
314
  throw e;
308
315
  });
309
- if (checkAccess && (!hasAccess(this, oldParent, constants.R_OK) || !hasAccess(this, newParent, constants.W_OK)))
316
+ assertRemovable(this, oldParent, src.stats, $ex);
317
+ if (newStats)
318
+ assertRemovable(this, newParent, newStats, $ex);
319
+ if (checkAccess && !hasAccess(this, newParent, constants.W_OK | constants.X_OK))
320
+ throw UV('EACCES', $ex);
321
+ if (checkAccess && isDirectory(src.stats) && srcDir != dstDir && !hasAccess(this, src.stats, constants.W_OK))
310
322
  throw UV('EACCES', $ex);
311
323
  if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
312
324
  throw UV('EISDIR', $ex);
313
325
  if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
314
326
  throw UV('ENOTDIR', $ex);
315
- const srcDir = dirname(src.path);
316
- const dstDir = dirname(dst.path);
317
327
  // Lock both parent directories, ordered by inode number to avoid ABBA deadlocks (like Linux's `lock_two_nondirectories`)
318
328
  const parents = [
319
329
  [srcDir, oldParent],
@@ -321,10 +331,10 @@ export async function rename(oldPath, newPath) {
321
331
  ];
322
332
  if (oldParent.ino > newParent.ino)
323
333
  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);
334
+ const _first = __addDisposableResource(env_4, await src.cache.lock(parents[0][0], 'rw', parents[0][1]), false);
335
+ const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : await src.cache.lock(parents[1][0], 'rw', parents[1][1]), false);
326
336
  await src.fs.rename(src.path, dst.path);
327
- cacheOf(fs).rename(src.path, dst.path);
337
+ src.cache.rename(src.path, dst.path);
328
338
  emitChange(this, 'rename', oldPath);
329
339
  emitChange(this, 'rename', newPath);
330
340
  }
@@ -342,24 +352,24 @@ export async function link(target, link) {
342
352
  target = normalizePath.call(this, target);
343
353
  link = normalizePath.call(this, link);
344
354
  const $ex = { syscall: 'link', path: target, dest: link };
345
- const { fs, path: resolved } = resolveMount(target, this, $ex);
346
- const dst = resolveMount(link, this, $ex);
355
+ const { fs, cache, path: resolved } = await resolve(this, target, true, $ex);
356
+ const dst = await resolve(this, link, true, $ex);
347
357
  if (fs.uuid != dst.fs.uuid)
348
358
  throw UV('EXDEV', $ex);
349
- const stats = await fs.stat(resolved);
359
+ const stats = await cache.stat(resolved, $ex);
350
360
  if (checkAccess) {
351
361
  if (!hasAccess(this, stats, constants.R_OK))
352
362
  throw UV('EACCES', $ex);
353
- const dirStats = await fs.stat(dirname(resolved));
363
+ const dirStats = await cache.stat(dirname(resolved), $ex);
354
364
  if (!hasAccess(this, dirStats, constants.R_OK))
355
365
  throw UV('EACCES', $ex);
356
- const destStats = await fs.stat(dirname(dst.path));
366
+ const destStats = await cache.stat(dirname(dst.path), $ex);
357
367
  if (!hasAccess(this, destStats, constants.W_OK))
358
368
  throw UV('EACCES', $ex);
359
369
  }
360
- const _ = __addDisposableResource(env_5, await lockPath(fs, dirname(dst.path), 'rw'), false);
370
+ const _ = __addDisposableResource(env_5, await cache.lock(dirname(dst.path), 'rw'), false);
361
371
  await fs.link(resolved, dst.path);
362
- cacheOf(fs).link(resolved, dst.path);
372
+ cache.link(resolved, dst.path);
363
373
  }
364
374
  catch (e_5) {
365
375
  env_5.error = e_5;
@@ -377,13 +387,16 @@ export async function stat(path, lstat) {
377
387
  stats = (await resolve(this, path, false, extra)).stats;
378
388
  else {
379
389
  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)));
390
+ const parent = await resolve(this, dir, false, extra);
391
+ if (parent.stats && isDirectory(parent.stats))
392
+ assertSearchable(this, parent.stats, extra);
393
+ const { root, mounts } = contextOf(this);
394
+ const mounted = base && mounts.get(join(root, parent.fullPath, base));
395
+ const fs = mounted || parent.fs;
396
+ const target = mounted ? '/' : join(parent.path, base);
397
+ stats = await cacheOf(fs).stat(target).catch(rethrow(extra));
383
398
  }
384
399
  if (!stats)
385
400
  throw UV('ENOENT', extra);
386
- if (checkAccess && !hasAccess(this, stats, constants.R_OK))
387
- throw UV('EACCES', extra);
388
401
  return stats;
389
402
  }
@@ -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
  }