@zenfs/core 2.7.5 → 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 (72) hide show
  1. package/dist/backends/backend.js +0 -2
  2. package/dist/backends/cow.d.ts +2 -2
  3. package/dist/backends/cow.js +6 -14
  4. package/dist/backends/passthrough.d.ts +0 -54
  5. package/dist/backends/passthrough.js +0 -54
  6. package/dist/backends/port.d.ts +2 -6
  7. package/dist/backends/port.js +3 -11
  8. package/dist/backends/store/fs.d.ts +0 -9
  9. package/dist/backends/store/fs.js +2 -24
  10. package/dist/backends/store/store.d.ts +1 -1
  11. package/dist/backends/store/store.js +2 -8
  12. package/dist/config.d.ts +7 -0
  13. package/dist/config.js +9 -3
  14. package/dist/internal/contexts.d.ts +1 -2
  15. package/dist/internal/credentials.d.ts +0 -3
  16. package/dist/internal/error.d.ts +5 -0
  17. package/dist/internal/error.js +10 -0
  18. package/dist/internal/file_index.js +0 -1
  19. package/dist/internal/index_fs.d.ts +2 -2
  20. package/dist/internal/index_fs.js +2 -2
  21. package/dist/internal/inode.d.ts +46 -43
  22. package/dist/internal/inode.js +75 -10
  23. package/dist/internal/rpc.d.ts +16 -7
  24. package/dist/internal/rpc.js +2 -11
  25. package/dist/mixins/async.js +22 -24
  26. package/dist/mixins/mutexed.d.ts +6 -41
  27. package/dist/mixins/mutexed.js +36 -109
  28. package/dist/mixins/sync.js +0 -2
  29. package/dist/node/async.js +2 -12
  30. package/dist/node/index.d.ts +1 -1
  31. package/dist/node/promises.d.ts +1 -6
  32. package/dist/node/promises.js +18 -22
  33. package/dist/node/stats.d.ts +25 -99
  34. package/dist/node/stats.js +22 -56
  35. package/dist/node/streams.d.ts +1 -37
  36. package/dist/node/sync.d.ts +1 -6
  37. package/dist/node/sync.js +16 -20
  38. package/dist/polyfills.js +25 -0
  39. package/dist/utils.d.ts +0 -4
  40. package/dist/vfs/acl.d.ts +1 -3
  41. package/dist/vfs/acl.js +1 -3
  42. package/dist/vfs/async.js +77 -69
  43. package/dist/vfs/config.d.ts +10 -1
  44. package/dist/vfs/config.js +5 -2
  45. package/dist/vfs/dir.d.ts +3 -0
  46. package/dist/vfs/dir.js +4 -1
  47. package/dist/vfs/file.d.ts +1 -8
  48. package/dist/vfs/file.js +11 -7
  49. package/dist/vfs/ioctl.js +2 -2
  50. package/dist/vfs/shared.d.ts +2 -0
  51. package/dist/vfs/shared.js +3 -4
  52. package/dist/vfs/sync.js +77 -61
  53. package/dist/vfs/vcache.d.ts +14 -14
  54. package/dist/vfs/vcache.js +65 -58
  55. package/dist/vfs/watchers.d.ts +1 -0
  56. package/dist/vfs/watchers.js +2 -4
  57. package/dist/vfs/xattr.js +18 -17
  58. package/package.json +2 -4
  59. package/scripts/make-index.js +1 -1
  60. package/tests/common/async.test.ts +35 -0
  61. package/tests/common/config.test.ts +1 -1
  62. package/tests/common/context.test.ts +1 -1
  63. package/tests/common/file_index.test.ts +1 -1
  64. package/tests/common/mounts.test.ts +12 -0
  65. package/tests/common/vcache.test.ts +13 -0
  66. package/tests/fs/concurrency.test.ts +3 -3
  67. package/tests/fs/links.test.ts +76 -0
  68. package/tests/fs/permissions.test.ts +232 -0
  69. package/tests/fs/stat.test.ts +1 -1
  70. package/tests/fs/streams.test.ts +1 -1
  71. package/tests/fs/xattr.test.ts +10 -0
  72. package/tests/common/mutex.test.ts +0 -61
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,39 +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
- // Stat it to make sure it exists. The vnode cache takes precedence since it may have unsynced changes
84
- const stats = cacheOf(resolved.fs).get(resolved.path)?.inode ?? (await resolved.fs.stat(resolved.path));
85
- if (!isSymbolicLink(stats)) {
86
- 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);
87
85
  }
88
- const target = resolvePath.call($, dirname(path), await readlink.call($, path));
89
- 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;
90
97
  }
91
- catch {
92
- // Go the long way
98
+ else if (base && checkAccess) {
99
+ const root = resolveMount('/', $);
100
+ assertSearchable($, await root.cache.stat(root.path), $ex);
93
101
  }
94
- const { base, dir } = parse(path);
95
- const realDir = dir == '/' ? '/' : (await resolve($, dir, false, extra)).fullPath;
96
102
  const maybePath = join(realDir, base);
97
103
  const resolved = resolveMount(maybePath, $);
98
- const stats = cacheOf(resolved.fs).get(resolved.path)?.inode
99
- ?? (await resolved.fs.stat(resolved.path).catch((e) => {
100
- if (e.code == 'ENOENT')
101
- return;
102
- throw setUVMessage(Object.assign(e, { syscall: 'stat', path: maybePath, ...extra }));
103
- }));
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
+ });
104
109
  if (!stats)
105
110
  return { ...resolved, fullPath: maybePath };
106
- if (!isSymbolicLink(stats)) {
111
+ if (!isSymbolicLink(stats) || preserveSymlinks) {
107
112
  return { ...resolved, fullPath: maybePath, stats };
108
113
  }
109
114
  const target = resolvePath.call($, realDir, await readlink.call($, maybePath));
@@ -118,7 +123,7 @@ export async function open($, path, opt) {
118
123
  const mode = normalizeMode(opt.mode, 0o644), flag = flags.parse(opt.flag);
119
124
  const $ex = { syscall: 'open', path };
120
125
  // eslint-disable-next-line prefer-const
121
- let { fs, path: resolved, stats } = await resolve($, path, opt.preserveSymlinks, $ex);
126
+ let { fs, cache, path: resolved, stats } = await resolve($, path, opt.preserveSymlinks, $ex);
122
127
  if (!stats) {
123
128
  const env_1 = { stack: [], error: void 0, hasError: false };
124
129
  try {
@@ -126,17 +131,16 @@ export async function open($, path, opt) {
126
131
  throw UV('ENOENT', $ex);
127
132
  // Create the file
128
133
  const parentPath = dirname(resolved);
129
- const parentStats = await fs.stat(parentPath);
130
- 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))
131
136
  throw UV('EACCES', 'open', dirname(path));
132
137
  if (!isDirectory(parentStats))
133
138
  throw UV('ENOTDIR', 'open', dirname(path));
134
139
  if (!opt.allowDirectory && isDirectory({ mode }))
135
140
  throw UV('EISDIR', 'open', path);
136
141
  // Serialize entry creation with other operations on the parent directory
137
- const _ = __addDisposableResource(env_1, await lockPath(fs, parentPath, 'rw', parentStats), false);
138
- // Another task may have created the file while we waited for the lock
139
- 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);
140
144
  if (!stats) {
141
145
  const { euid: uid, egid: gid } = contextOf($).credentials;
142
146
  const inode = await fs.createFile(resolved, {
@@ -144,9 +148,8 @@ export async function open($, path, opt) {
144
148
  uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
145
149
  gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
146
150
  });
147
- // A new entry in the parent directory, which is a 'rename' event
148
151
  emitChange($, 'rename', path);
149
- return new Handle($, path, resolved, flag, cacheOf(fs).ref(resolved, inode));
152
+ return new Handle($, path, resolved, flag, cache.ref(resolved, inode));
150
153
  }
151
154
  }
152
155
  catch (e_1) {
@@ -163,7 +166,7 @@ export async function open($, path, opt) {
163
166
  throw UV('EEXIST', $ex);
164
167
  if (!opt.allowDirectory && isDirectory({ mode }))
165
168
  throw UV('EISDIR', 'open', path);
166
- const handle = new Handle($, path, resolved, flag, cacheOf(fs).ref(resolved, stats));
169
+ const handle = new Handle($, path, resolved, flag, cache.ref(resolved, stats));
167
170
  if (flag & constants.O_TRUNC)
168
171
  await handle.truncate(0);
169
172
  return handle;
@@ -189,17 +192,17 @@ export async function mkdir(path, options = {}) {
189
192
  const { mode = 0o777, recursive } = options;
190
193
  const $ex = { syscall: 'mkdir', path: original };
191
194
  const { fullPath: realParent } = await resolve(this, dirname(original), false, $ex);
192
- const { fs, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
195
+ const { fs, cache, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
193
196
  const followed = realParent != dirname(original);
194
197
  let firstCreated;
195
198
  const __create = async (path, resolved) => {
196
199
  const env_2 = { stack: [], error: void 0, hasError: false };
197
200
  try {
198
201
  const parentPath = dirname(resolved);
199
- const parent = recursive && parentPath != '/' ? await __create(dirname(path), parentPath) : await fs.stat(parentPath);
200
- 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);
201
204
  if (recursive) {
202
- const existing = cacheOf(fs).get(resolved)?.inode ?? (await fs.stat(resolved).catch(() => null));
205
+ const existing = await cache.stat(resolved).catch(() => null);
203
206
  if (existing) {
204
207
  const stats = isSymbolicLink(existing) ? (await resolve(this, path, false, $ex)).stats : existing;
205
208
  if (!stats)
@@ -212,7 +215,7 @@ export async function mkdir(path, options = {}) {
212
215
  if (followed && (await fs.exists(join(parentPath, basename(path)))))
213
216
  throw UV('ENOTDIR', 'mkdir', path);
214
217
  }
215
- if (checkAccess && !hasAccess(this, parent, constants.W_OK))
218
+ if (checkAccess && !hasAccess(this, parent, constants.W_OK | constants.X_OK))
216
219
  throw UV('EACCES', 'mkdir', path);
217
220
  const inode = await fs
218
221
  .mkdir(resolved, {
@@ -239,9 +242,8 @@ export async function mkdir(path, options = {}) {
239
242
  }
240
243
  export async function readdir(path, options = {}) {
241
244
  path = normalizePath.call(this, path);
242
- // Node reports `readdir` failures as `scandir`
243
245
  const $ex = { syscall: 'scandir', path };
244
- const { fs, path: resolved, stats } = await resolve(this, path, false, $ex);
246
+ const { fs, cache, path: resolved, stats } = await resolve(this, path, false, $ex);
245
247
  if (!stats)
246
248
  throw UV('ENOENT', $ex);
247
249
  if (checkAccess && !hasAccess(this, stats, constants.R_OK))
@@ -252,7 +254,7 @@ export async function readdir(path, options = {}) {
252
254
  {
253
255
  const env_3 = { stack: [], error: void 0, hasError: false };
254
256
  try {
255
- const _ = __addDisposableResource(env_3, await lockPath(fs, resolved, 'ro', stats), false);
257
+ const _ = __addDisposableResource(env_3, await cache.lock(resolved, 'ro', stats), false);
256
258
  entries = await fs.readdir(resolved);
257
259
  }
258
260
  catch (e_3) {
@@ -265,7 +267,7 @@ export async function readdir(path, options = {}) {
265
267
  }
266
268
  const values = [];
267
269
  const addEntry = async (entry) => {
268
- const entryStats = await fs.stat(join(resolved, entry)).catch((e) => {
270
+ const entryStats = await cache.stat(join(resolved, entry), $ex).catch((e) => {
269
271
  if (e.code == 'ENOENT')
270
272
  return;
271
273
  throw e;
@@ -294,7 +296,7 @@ export async function rename(oldPath, newPath) {
294
296
  newPath = normalizePath.call(this, newPath);
295
297
  const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
296
298
  const src = await resolve(this, oldPath, true, $ex);
297
- const dst = resolveMount(newPath, this, $ex);
299
+ const dst = await resolve(this, newPath, true, $ex);
298
300
  if (src.fs.uuid !== dst.fs.uuid)
299
301
  throw UV('EXDEV', $ex);
300
302
  // A directory can not be moved inside itself
@@ -302,22 +304,26 @@ export async function rename(oldPath, newPath) {
302
304
  throw UV('EINVAL', $ex);
303
305
  if (!src.stats)
304
306
  throw UV('ENOENT', $ex);
305
- const fs = src.fs;
306
- const oldParent = await fs.stat(dirname(src.path));
307
- const newParent = await fs.stat(dirname(dst.path));
308
- 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) => {
309
312
  if (e.code == 'ENOENT')
310
313
  return null;
311
314
  throw e;
312
315
  });
313
- 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))
314
322
  throw UV('EACCES', $ex);
315
323
  if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
316
324
  throw UV('EISDIR', $ex);
317
325
  if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
318
326
  throw UV('ENOTDIR', $ex);
319
- const srcDir = dirname(src.path);
320
- const dstDir = dirname(dst.path);
321
327
  // Lock both parent directories, ordered by inode number to avoid ABBA deadlocks (like Linux's `lock_two_nondirectories`)
322
328
  const parents = [
323
329
  [srcDir, oldParent],
@@ -325,11 +331,10 @@ export async function rename(oldPath, newPath) {
325
331
  ];
326
332
  if (oldParent.ino > newParent.ino)
327
333
  parents.reverse();
328
- const _first = __addDisposableResource(env_4, await lockPath(fs, parents[0][0], 'rw', parents[0][1]), false);
329
- 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);
330
336
  await src.fs.rename(src.path, dst.path);
331
- cacheOf(fs).rename(src.path, dst.path);
332
- // Both names change which entries exist, so both are 'rename' events
337
+ src.cache.rename(src.path, dst.path);
333
338
  emitChange(this, 'rename', oldPath);
334
339
  emitChange(this, 'rename', newPath);
335
340
  }
@@ -347,24 +352,24 @@ export async function link(target, link) {
347
352
  target = normalizePath.call(this, target);
348
353
  link = normalizePath.call(this, link);
349
354
  const $ex = { syscall: 'link', path: target, dest: link };
350
- const { fs, path: resolved } = resolveMount(target, this, $ex);
351
- 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);
352
357
  if (fs.uuid != dst.fs.uuid)
353
358
  throw UV('EXDEV', $ex);
354
- const stats = await fs.stat(resolved);
359
+ const stats = await cache.stat(resolved, $ex);
355
360
  if (checkAccess) {
356
361
  if (!hasAccess(this, stats, constants.R_OK))
357
362
  throw UV('EACCES', $ex);
358
- const dirStats = await fs.stat(dirname(resolved));
363
+ const dirStats = await cache.stat(dirname(resolved), $ex);
359
364
  if (!hasAccess(this, dirStats, constants.R_OK))
360
365
  throw UV('EACCES', $ex);
361
- const destStats = await fs.stat(dirname(dst.path));
366
+ const destStats = await cache.stat(dirname(dst.path), $ex);
362
367
  if (!hasAccess(this, destStats, constants.W_OK))
363
368
  throw UV('EACCES', $ex);
364
369
  }
365
- 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);
366
371
  await fs.link(resolved, dst.path);
367
- cacheOf(fs).link(resolved, dst.path);
372
+ cache.link(resolved, dst.path);
368
373
  }
369
374
  catch (e_5) {
370
375
  env_5.error = e_5;
@@ -382,13 +387,16 @@ export async function stat(path, lstat) {
382
387
  stats = (await resolve(this, path, false, extra)).stats;
383
388
  else {
384
389
  const { base, dir } = parse(path);
385
- const { fs, path: parent } = await resolve(this, dir, false, extra);
386
- const target = base ? join(parent, base) : parent;
387
- 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));
388
398
  }
389
399
  if (!stats)
390
400
  throw UV('ENOENT', extra);
391
- if (checkAccess && !hasAccess(this, stats, constants.R_OK))
392
- throw UV('EACCES', extra);
393
401
  return stats;
394
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/dir.d.ts CHANGED
@@ -22,6 +22,9 @@ export declare function ifToDt(mode: number): DirType;
22
22
  * @see `DTTOIF` in `dirent.h`
23
23
  */
24
24
  export declare function dtToIf(dt: DirType): number;
25
+ /**
26
+ * @todo [breaking] change this to an interface
27
+ */
25
28
  export declare class Dirent {
26
29
  ino: number;
27
30
  type: DirType;
package/dist/vfs/dir.js CHANGED
@@ -19,7 +19,7 @@ export var DirType;
19
19
  * @see `IFTODT` in `dirent.h`
20
20
  */
21
21
  export function ifToDt(mode) {
22
- return ((mode & 0o170000) >> 12);
22
+ return (mode & 0o170000) >> 12;
23
23
  }
24
24
  /**
25
25
  * Converts a directory type to a file mode.
@@ -28,6 +28,9 @@ export function ifToDt(mode) {
28
28
  export function dtToIf(dt) {
29
29
  return dt << 12;
30
30
  }
31
+ /**
32
+ * @todo [breaking] change this to an interface
33
+ */
31
34
  export class Dirent {
32
35
  ino;
33
36
  type;
@@ -3,11 +3,6 @@ import type { FileSystem, StreamOptions } from '../internal/filesystem.js';
3
3
  import { type InodeLike } from '../internal/inode.js';
4
4
  import '../polyfills.js';
5
5
  import type { VNode } from './vnode.js';
6
- /** @hidden */
7
- export interface FileReadResult<T extends ArrayBufferView> {
8
- bytesRead: number;
9
- buffer: T;
10
- }
11
6
  /**
12
7
  * @internal
13
8
  */
@@ -17,9 +12,7 @@ export declare class Handle {
17
12
  readonly internalPath: string;
18
13
  readonly flag: number;
19
14
  readonly vnode: VNode;
20
- /**
21
- * Current position
22
- */
15
+ /** Current position */
23
16
  protected _position: number;
24
17
  /**
25
18
  * Get the current file position.
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';
@@ -114,9 +114,7 @@ let Handle = (() => {
114
114
  internalPath;
115
115
  flag;
116
116
  vnode;
117
- /**
118
- * Current position
119
- */
117
+ /** Current position */
120
118
  _position = 0;
121
119
  /**
122
120
  * Get the current file position.
@@ -330,9 +328,10 @@ let Handle = (() => {
330
328
  try {
331
329
  if (this.closed)
332
330
  throw UV('EBADF', 'chmod', this.path);
331
+ assertOwner(this.context, this.inode, { syscall: 'chmod', path: this.path });
333
332
  const _ = __addDisposableResource(env_5, this.vnode.lockSync('rw'), false);
334
333
  this.vnode.metadataDirty = true;
335
- this.inode.mode = (this.inode.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
334
+ _chmod(this.context, this.inode, mode);
336
335
  if (this._isSync || mode > c.S_IFMT)
337
336
  this.vnode.syncSync();
338
337
  }
@@ -348,7 +347,8 @@ let Handle = (() => {
348
347
  const env_6 = { stack: [], error: void 0, hasError: false };
349
348
  try {
350
349
  if (this.closed)
351
- 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 });
352
352
  const _ = __addDisposableResource(env_6, this.vnode.lockSync('rw'), false);
353
353
  this.vnode.metadataDirty = true;
354
354
  _chown(this.inode, uid, gid);
@@ -371,6 +371,7 @@ let Handle = (() => {
371
371
  try {
372
372
  if (this.closed)
373
373
  throw UV('EBADF', 'utimes', this.path);
374
+ assertOwner(this.context, this.inode, { syscall: 'utimes', path: this.path });
374
375
  const _ = __addDisposableResource(env_7, this.vnode.lockSync('rw'), false);
375
376
  this.vnode.metadataDirty = true;
376
377
  this.inode.atimeMs = atime;
@@ -544,9 +545,10 @@ let Handle = (() => {
544
545
  try {
545
546
  if (this.closed)
546
547
  throw UV('EBADF', 'chmod', this.path);
548
+ assertOwner(this.context, this.inode, { syscall: 'chmod', path: this.path });
547
549
  const _ = __addDisposableResource(env_12, await this.vnode.lock('rw'), false);
548
550
  this.vnode.metadataDirty = true;
549
- this.inode.mode = (this.inode.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
551
+ _chmod(this.context, this.inode, mode);
550
552
  if (this._isSync || mode > c.S_IFMT)
551
553
  await this.vnode.sync();
552
554
  }
@@ -563,6 +565,7 @@ let Handle = (() => {
563
565
  try {
564
566
  if (this.closed)
565
567
  throw UV('EBADF', 'chown', this.path);
568
+ assertCanChown(this.context, this.inode, uid, gid, { syscall: 'chown', path: this.path });
566
569
  const _ = __addDisposableResource(env_13, await this.vnode.lock('rw'), false);
567
570
  this.vnode.metadataDirty = true;
568
571
  _chown(this.inode, uid, gid);
@@ -585,6 +588,7 @@ let Handle = (() => {
585
588
  try {
586
589
  if (this.closed)
587
590
  throw UV('EBADF', 'utimes', this.path);
591
+ assertOwner(this.context, this.inode, { syscall: 'utimes', path: this.path });
588
592
  const _ = __addDisposableResource(env_14, await this.vnode.lock('rw'), false);
589
593
  this.vnode.metadataDirty = true;
590
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
  }