@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.
- package/README.md +5 -0
- package/dist/backends/index.d.ts +3 -1
- package/dist/backends/index.js +3 -1
- package/dist/backends/single_buffer/backend.d.ts +42 -0
- package/dist/backends/single_buffer/backend.js +204 -0
- package/dist/backends/{single_buffer.d.ts → single_buffer/v1.d.ts} +24 -34
- package/dist/backends/{single_buffer.js → single_buffer/v1.js} +10 -26
- package/dist/backends/single_buffer/v2.d.ts +257 -0
- package/dist/backends/single_buffer/v2.js +1283 -0
- package/dist/backends/store/fs.d.ts +1 -1
- package/dist/backends/store/fs.js +35 -18
- package/dist/backends/store/map.d.ts +6 -6
- package/dist/backends/store/map.js +6 -6
- package/dist/backends/store/store.d.ts +18 -14
- package/dist/backends/store/store.js +12 -14
- package/dist/config.d.ts +7 -0
- package/dist/config.js +9 -3
- package/dist/internal/contexts.d.ts +5 -0
- package/dist/internal/contexts.js +7 -1
- package/dist/internal/error.d.ts +5 -0
- package/dist/internal/error.js +10 -0
- package/dist/internal/inode.d.ts +25 -0
- package/dist/internal/inode.js +75 -10
- package/dist/mixins/async.js +47 -30
- package/dist/node/promises.js +23 -124
- package/dist/node/stats.d.ts +1 -1
- package/dist/node/stats.js +4 -1
- package/dist/node/sync.js +20 -51
- package/dist/polyfills.js +16 -0
- package/dist/vfs/async.d.ts +4 -1
- package/dist/vfs/async.js +149 -69
- package/dist/vfs/config.d.ts +10 -1
- package/dist/vfs/config.js +5 -2
- package/dist/vfs/file.js +10 -4
- 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.d.ts +4 -1
- package/dist/vfs/sync.js +142 -64
- package/dist/vfs/vcache.d.ts +14 -14
- package/dist/vfs/vcache.js +65 -57
- 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 +3 -4
- package/scripts/make-index.js +1 -1
- package/tests/backend/single-buffer-rotate.worker.js +1 -1
- package/tests/backend/single-buffer-v2.test.ts +342 -0
- package/tests/backend/single-buffer-v2.worker.js +51 -0
- package/tests/backend/single-buffer.test.ts +6 -6
- package/tests/backend/single-buffer.worker.js +1 -0
- package/tests/common/async.test.ts +46 -0
- package/tests/common/config.test.ts +1 -1
- package/tests/common/context.test.ts +30 -0
- 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 +253 -0
- package/tests/fs/stat.test.ts +1 -1
- package/tests/fs/streams.test.ts +1 -1
- package/tests/fs/watch.test.ts +14 -0
- package/tests/fs/xattr.test.ts +10 -0
- package/tests/perf/config.json +31 -1
- package/tests/perf/single-buffer-random.ts +38 -0
- package/tests/perf/single-buffer-read.ts +28 -0
- package/tests/perf/single-buffer-write.ts +41 -0
- package/tests/setup/single-buffer-v2.ts +10 -0
- 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
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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 =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|
|
137
|
-
stats =
|
|
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,
|
|
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(
|
|
167
|
+
if (!opt.allowDirectory && isDirectory(stats))
|
|
162
168
|
throw UV('EISDIR', 'open', path);
|
|
163
|
-
const handle = new Handle($, path, resolved, flag,
|
|
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 {
|
|
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
|
|
197
|
-
const _ = __addDisposableResource(env_2, await
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const
|
|
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
|
-
|
|
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
|
|
325
|
-
const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : await
|
|
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
|
-
|
|
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 } =
|
|
346
|
-
const dst =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
371
|
+
const _ = __addDisposableResource(env_5, await cache.lock(dirname(dst.path), 'rw'), false);
|
|
361
372
|
await fs.link(resolved, dst.path);
|
|
362
|
-
|
|
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
|
|
381
|
-
|
|
382
|
-
|
|
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
|
+
}
|
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/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.
|
|
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', '
|
|
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.
|
|
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 =
|
|
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
|
}
|
package/dist/vfs/sync.d.ts
CHANGED
|
@@ -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;
|