@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.
- package/dist/backends/backend.js +0 -2
- package/dist/backends/cow.d.ts +2 -2
- package/dist/backends/cow.js +6 -14
- package/dist/backends/passthrough.d.ts +0 -54
- package/dist/backends/passthrough.js +0 -54
- package/dist/backends/port.d.ts +2 -6
- package/dist/backends/port.js +3 -11
- package/dist/backends/store/fs.d.ts +0 -9
- package/dist/backends/store/fs.js +2 -24
- package/dist/backends/store/store.d.ts +1 -1
- package/dist/backends/store/store.js +2 -8
- package/dist/config.d.ts +7 -0
- package/dist/config.js +9 -3
- package/dist/internal/contexts.d.ts +1 -2
- package/dist/internal/credentials.d.ts +0 -3
- package/dist/internal/error.d.ts +5 -0
- package/dist/internal/error.js +10 -0
- package/dist/internal/file_index.js +0 -1
- package/dist/internal/index_fs.d.ts +2 -2
- package/dist/internal/index_fs.js +2 -2
- package/dist/internal/inode.d.ts +46 -43
- package/dist/internal/inode.js +75 -10
- package/dist/internal/rpc.d.ts +16 -7
- package/dist/internal/rpc.js +2 -11
- package/dist/mixins/async.js +22 -24
- package/dist/mixins/mutexed.d.ts +6 -41
- package/dist/mixins/mutexed.js +36 -109
- package/dist/mixins/sync.js +0 -2
- package/dist/node/async.js +2 -12
- package/dist/node/index.d.ts +1 -1
- package/dist/node/promises.d.ts +1 -6
- package/dist/node/promises.js +18 -22
- package/dist/node/stats.d.ts +25 -99
- package/dist/node/stats.js +22 -56
- package/dist/node/streams.d.ts +1 -37
- package/dist/node/sync.d.ts +1 -6
- package/dist/node/sync.js +16 -20
- package/dist/polyfills.js +25 -0
- package/dist/utils.d.ts +0 -4
- package/dist/vfs/acl.d.ts +1 -3
- package/dist/vfs/acl.js +1 -3
- package/dist/vfs/async.js +77 -69
- package/dist/vfs/config.d.ts +10 -1
- package/dist/vfs/config.js +5 -2
- package/dist/vfs/dir.d.ts +3 -0
- package/dist/vfs/dir.js +4 -1
- package/dist/vfs/file.d.ts +1 -8
- package/dist/vfs/file.js +11 -7
- package/dist/vfs/ioctl.js +2 -2
- package/dist/vfs/shared.d.ts +2 -0
- package/dist/vfs/shared.js +3 -4
- package/dist/vfs/sync.js +77 -61
- package/dist/vfs/vcache.d.ts +14 -14
- package/dist/vfs/vcache.js +65 -58
- package/dist/vfs/watchers.d.ts +1 -0
- package/dist/vfs/watchers.js +2 -4
- package/dist/vfs/xattr.js +18 -17
- package/package.json +2 -4
- package/scripts/make-index.js +1 -1
- package/tests/common/async.test.ts +35 -0
- package/tests/common/config.test.ts +1 -1
- package/tests/common/context.test.ts +1 -1
- package/tests/common/file_index.test.ts +1 -1
- package/tests/common/mounts.test.ts +12 -0
- package/tests/common/vcache.test.ts +13 -0
- package/tests/fs/concurrency.test.ts +3 -3
- package/tests/fs/links.test.ts +76 -0
- package/tests/fs/permissions.test.ts +232 -0
- package/tests/fs/stat.test.ts +1 -1
- package/tests/fs/streams.test.ts +1 -1
- package/tests/fs/xattr.test.ts +10 -0
- 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
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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 =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
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
|
|
138
|
-
|
|
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,
|
|
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,
|
|
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
|
|
200
|
-
const _ = __addDisposableResource(env_2, await
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const
|
|
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
|
-
|
|
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
|
|
329
|
-
const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : await
|
|
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
|
-
|
|
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 } =
|
|
351
|
-
const dst =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
370
|
+
const _ = __addDisposableResource(env_5, await cache.lock(dirname(dst.path), 'rw'), false);
|
|
366
371
|
await fs.link(resolved, dst.path);
|
|
367
|
-
|
|
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
|
|
386
|
-
|
|
387
|
-
|
|
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
|
}
|
package/dist/vfs/config.d.ts
CHANGED
|
@@ -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
|
|
8
|
+
export interface _VFSConfig {
|
|
9
|
+
checkAccess: boolean;
|
|
10
|
+
resolveFullWalk: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* @internal @hidden
|
|
14
|
+
*/
|
|
15
|
+
export declare function _setVFSConfig(cfg: _VFSConfig): void;
|
package/dist/vfs/config.js
CHANGED
|
@@ -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
|
|
8
|
-
checkAccess =
|
|
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 (
|
|
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;
|
package/dist/vfs/file.d.ts
CHANGED
|
@@ -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.
|
|
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', '
|
|
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.
|
|
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 =
|
|
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 =
|
|
71
|
+
vcache = mnt.cache;
|
|
72
72
|
vnode = vcache.ref(mnt.path, vcache.get(mnt.path)?.inode ?? mnt.stats);
|
|
73
73
|
}
|
|
74
74
|
const context = {
|
package/dist/vfs/shared.d.ts
CHANGED
|
@@ -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;
|
package/dist/vfs/shared.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|