@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/sync.js
CHANGED
|
@@ -53,13 +53,13 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
53
53
|
import { 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';
|
|
56
|
+
import { applyUmask, contextOf } from '../internal/contexts.js';
|
|
57
57
|
import { wrap } from '../internal/error.js';
|
|
58
|
-
import { hasAccess, isDirectory, isSymbolicLink } from '../internal/inode.js';
|
|
58
|
+
import { assertRemovable, assertSearchable, hasAccess, isDirectory, isSymbolicLink } from '../internal/inode.js';
|
|
59
59
|
import { basename, dirname, join, parse, resolve as resolvePath } from '../path.js';
|
|
60
|
-
import { normalizeMode, normalizePath } from '../utils.js';
|
|
61
|
-
import { cacheOf
|
|
62
|
-
import { checkAccess } from './config.js';
|
|
60
|
+
import { normalizeMode, normalizePath, normalizeTime } from '../utils.js';
|
|
61
|
+
import { cacheOf } from './vcache.js';
|
|
62
|
+
import { checkAccess, resolveFullWalk } from './config.js';
|
|
63
63
|
import { Dirent, ifToDt } from './dir.js';
|
|
64
64
|
import { Handle } from './file.js';
|
|
65
65
|
import * as flags from './flags.js';
|
|
@@ -75,35 +75,45 @@ export function resolve($, path, preserveSymlinks, extra) {
|
|
|
75
75
|
path = resolvePath.call($, path);
|
|
76
76
|
/* Try to resolve it directly. If this works,
|
|
77
77
|
that means we don't need to perform any resolution for parent directories. */
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
if (!resolveFullWalk)
|
|
79
|
+
try {
|
|
80
|
+
const resolved = resolveMount(path, $);
|
|
81
|
+
// Stat it to make sure it exists. The vnode cache takes precedence since it may have unsynced changes
|
|
82
|
+
const stats = resolved.cache.statSync(resolved.path);
|
|
83
|
+
if (!isSymbolicLink(stats) || preserveSymlinks) {
|
|
84
|
+
return { ...resolved, fullPath: path, stats };
|
|
85
|
+
}
|
|
86
|
+
const target = resolvePath.call($, dirname(path), readlink.call($, path));
|
|
87
|
+
return resolve($, target, preserveSymlinks, extra);
|
|
84
88
|
}
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
catch {
|
|
90
|
+
// Go the long way
|
|
91
|
+
}
|
|
92
|
+
const { base, dir } = parse(path);
|
|
93
|
+
const $ex = { syscall: 'stat', path, ...extra };
|
|
94
|
+
let realDir = '/';
|
|
95
|
+
if (dir != '/') {
|
|
96
|
+
const parent = resolve($, dir, false, extra);
|
|
97
|
+
if (parent.stats && isDirectory(parent.stats))
|
|
98
|
+
assertSearchable($, parent.stats, $ex);
|
|
99
|
+
realDir = parent.fullPath;
|
|
87
100
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
throw e;
|
|
101
|
+
else if (base && checkAccess) {
|
|
102
|
+
const root = resolveMount('/', $);
|
|
103
|
+
assertSearchable($, root.cache.statSync(root.path), $ex);
|
|
92
104
|
}
|
|
93
|
-
const { base, dir } = parse(path);
|
|
94
|
-
const realDir = dir == '/' ? '/' : resolve($, dir, false, extra).fullPath;
|
|
95
105
|
const maybePath = join(realDir, base);
|
|
96
106
|
const resolved = resolveMount(maybePath, $);
|
|
97
107
|
let stats;
|
|
98
108
|
try {
|
|
99
|
-
stats =
|
|
109
|
+
stats = resolved.cache.statSync(resolved.path);
|
|
100
110
|
}
|
|
101
111
|
catch (e) {
|
|
102
112
|
if (e.code === 'ENOENT')
|
|
103
113
|
return { ...resolved, fullPath: maybePath };
|
|
104
114
|
throw setUVMessage(Object.assign(e, { syscall: 'stat', path: maybePath, ...extra }));
|
|
105
115
|
}
|
|
106
|
-
if (!isSymbolicLink(stats)) {
|
|
116
|
+
if (!isSymbolicLink(stats) || preserveSymlinks) {
|
|
107
117
|
return { ...resolved, fullPath: maybePath, stats };
|
|
108
118
|
}
|
|
109
119
|
const target = resolvePath.call($, realDir, readlink.call($, maybePath));
|
|
@@ -116,11 +126,11 @@ export function resolve($, path, preserveSymlinks, extra) {
|
|
|
116
126
|
export function open($, path, opt) {
|
|
117
127
|
path = normalizePath.call($, path);
|
|
118
128
|
const mode = normalizeMode(opt.mode, 0o644), flag = flags.parse(opt.flag);
|
|
119
|
-
path =
|
|
120
|
-
const { fs, path: resolved } = resolveMount(path, $);
|
|
129
|
+
path = resolve($, path, opt.preserveSymlinks).fullPath;
|
|
130
|
+
const { fs, cache, path: resolved } = resolveMount(path, $);
|
|
121
131
|
let stats;
|
|
122
132
|
try {
|
|
123
|
-
stats =
|
|
133
|
+
stats = cache.statSync(resolved);
|
|
124
134
|
}
|
|
125
135
|
catch {
|
|
126
136
|
// nothing
|
|
@@ -133,8 +143,8 @@ export function open($, path, opt) {
|
|
|
133
143
|
}
|
|
134
144
|
// Create the file
|
|
135
145
|
const parentPath = dirname(resolved);
|
|
136
|
-
const parentStats =
|
|
137
|
-
if (checkAccess && !hasAccess($, parentStats, constants.W_OK)) {
|
|
146
|
+
const parentStats = cache.statSync(parentPath, { syscall: 'open', path });
|
|
147
|
+
if (checkAccess && !hasAccess($, parentStats, constants.W_OK | constants.X_OK)) {
|
|
138
148
|
throw UV('EACCES', 'open', path);
|
|
139
149
|
}
|
|
140
150
|
if (!isDirectory(parentStats)) {
|
|
@@ -143,16 +153,16 @@ export function open($, path, opt) {
|
|
|
143
153
|
if (!opt.allowDirectory && isDirectory({ mode }))
|
|
144
154
|
throw UV('EISDIR', 'open', path);
|
|
145
155
|
// Serialize entry creation with other operations on the parent directory
|
|
146
|
-
const _ = __addDisposableResource(env_1,
|
|
156
|
+
const _ = __addDisposableResource(env_1, cache.lockSync(parentPath, 'rw', parentStats), false);
|
|
147
157
|
const { euid: uid, egid: gid } = contextOf($).credentials;
|
|
148
158
|
const inode = fs.createFileSync(resolved, {
|
|
149
|
-
mode,
|
|
159
|
+
mode: applyUmask($, mode),
|
|
150
160
|
uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
|
|
151
161
|
gid: parentStats.mode & constants.S_ISGID ? parentStats.gid : gid,
|
|
152
162
|
});
|
|
153
163
|
// A new entry in the parent directory, which is a 'rename' event
|
|
154
164
|
emitChange($, 'rename', path);
|
|
155
|
-
return new Handle($, path, resolved, flag,
|
|
165
|
+
return new Handle($, path, resolved, flag, cache.ref(resolved, inode));
|
|
156
166
|
}
|
|
157
167
|
catch (e_1) {
|
|
158
168
|
env_1.error = e_1;
|
|
@@ -162,14 +172,14 @@ export function open($, path, opt) {
|
|
|
162
172
|
__disposeResources(env_1);
|
|
163
173
|
}
|
|
164
174
|
}
|
|
165
|
-
if (checkAccess &&
|
|
175
|
+
if (checkAccess && !hasAccess($, stats, flags.toMode(flag))) {
|
|
166
176
|
throw UV('EACCES', 'open', path);
|
|
167
177
|
}
|
|
168
178
|
if (flag & constants.O_EXCL)
|
|
169
179
|
throw UV('EEXIST', 'open', path);
|
|
170
180
|
if (!opt.allowDirectory && isDirectory(stats))
|
|
171
181
|
throw UV('EISDIR', 'open', path);
|
|
172
|
-
const file = new Handle($, path, resolved, flag,
|
|
182
|
+
const file = new Handle($, path, resolved, flag, cache.ref(resolved, stats));
|
|
173
183
|
if (flag & constants.O_TRUNC)
|
|
174
184
|
file.truncateSync(0);
|
|
175
185
|
return file;
|
|
@@ -192,20 +202,21 @@ export function mkdir(path, options = {}) {
|
|
|
192
202
|
const original = normalizePath.call(this, path);
|
|
193
203
|
const $ex = { syscall: 'mkdir', path: original };
|
|
194
204
|
const { fullPath: realParent } = resolve(this, dirname(original), false, $ex);
|
|
195
|
-
const { fs, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
|
|
205
|
+
const { fs, cache, path: target } = resolveMount(join(realParent, basename(original)), this, $ex);
|
|
196
206
|
const followed = realParent != dirname(original);
|
|
197
207
|
const { euid: uid, egid: gid } = contextOf(this).credentials;
|
|
198
|
-
const {
|
|
208
|
+
const { recursive } = options;
|
|
209
|
+
const mode = applyUmask(this, options.mode ?? 0o777);
|
|
199
210
|
let firstCreated;
|
|
200
211
|
const __create = (path, resolved) => {
|
|
201
212
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
202
213
|
try {
|
|
203
214
|
const parentPath = dirname(resolved);
|
|
204
|
-
const parent = recursive && parentPath != '/' ? __create(dirname(path), parentPath) :
|
|
215
|
+
const parent = recursive && parentPath != '/' ? __create(dirname(path), parentPath) : cache.statSync(parentPath, $ex);
|
|
205
216
|
if (recursive) {
|
|
206
217
|
let existing;
|
|
207
218
|
try {
|
|
208
|
-
existing =
|
|
219
|
+
existing = cache.statSync(resolved);
|
|
209
220
|
}
|
|
210
221
|
catch {
|
|
211
222
|
// It doesn't exist yet
|
|
@@ -221,9 +232,9 @@ export function mkdir(path, options = {}) {
|
|
|
221
232
|
if (followed && fs.existsSync(join(parentPath, basename(path))))
|
|
222
233
|
throw UV('ENOENT', $ex);
|
|
223
234
|
}
|
|
224
|
-
if (checkAccess && !hasAccess(this, parent, constants.W_OK))
|
|
235
|
+
if (checkAccess && !hasAccess(this, parent, constants.W_OK | constants.X_OK))
|
|
225
236
|
throw UV('EACCES', 'mkdir', path);
|
|
226
|
-
const _ = __addDisposableResource(env_2,
|
|
237
|
+
const _ = __addDisposableResource(env_2, cache.lockSync(parentPath, 'rw', parent), false);
|
|
227
238
|
const inode = wrap(fs, 'mkdirSync', { path, syscall: 'mkdir' })(resolved, {
|
|
228
239
|
mode,
|
|
229
240
|
uid: parent.mode & constants.S_ISUID ? parent.uid : uid,
|
|
@@ -247,9 +258,9 @@ export function mkdir(path, options = {}) {
|
|
|
247
258
|
}
|
|
248
259
|
export function readdir(path, options = {}) {
|
|
249
260
|
path = normalizePath.call(this, path);
|
|
250
|
-
const { fs, path: resolved } = resolve(this, path);
|
|
261
|
+
const { fs, cache, path: resolved } = resolve(this, path);
|
|
251
262
|
// Node reports `readdir` failures as `scandir`
|
|
252
|
-
const stats =
|
|
263
|
+
const stats = cache.statSync(resolved, { path, syscall: 'scandir' });
|
|
253
264
|
if (checkAccess && !hasAccess(this, stats, constants.R_OK))
|
|
254
265
|
throw UV('EACCES', 'scandir', path);
|
|
255
266
|
if (!isDirectory(stats))
|
|
@@ -258,7 +269,7 @@ export function readdir(path, options = {}) {
|
|
|
258
269
|
{
|
|
259
270
|
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
260
271
|
try {
|
|
261
|
-
const _ = __addDisposableResource(env_3,
|
|
272
|
+
const _ = __addDisposableResource(env_3, cache.lockSync(resolved, 'ro', stats), false);
|
|
262
273
|
entries = fs.readdirSync(resolved);
|
|
263
274
|
}
|
|
264
275
|
catch (e_3) {
|
|
@@ -273,7 +284,7 @@ export function readdir(path, options = {}) {
|
|
|
273
284
|
const addEntry = (entry) => {
|
|
274
285
|
let entryStat;
|
|
275
286
|
try {
|
|
276
|
-
entryStat =
|
|
287
|
+
entryStat = cache.statSync(join(resolved, entry), { syscall: 'scandir', path });
|
|
277
288
|
}
|
|
278
289
|
catch (e) {
|
|
279
290
|
if (e.code == 'ENOENT')
|
|
@@ -303,7 +314,7 @@ export function rename(oldPath, newPath) {
|
|
|
303
314
|
newPath = normalizePath.call(this, newPath);
|
|
304
315
|
const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
|
|
305
316
|
const src = resolve(this, oldPath, true, $ex);
|
|
306
|
-
const dst =
|
|
317
|
+
const dst = resolve(this, newPath, true, $ex);
|
|
307
318
|
if (src.fs.uuid !== dst.fs.uuid)
|
|
308
319
|
throw UV('EXDEV', $ex);
|
|
309
320
|
// A directory can not be moved inside itself
|
|
@@ -311,25 +322,29 @@ export function rename(oldPath, newPath) {
|
|
|
311
322
|
throw UV('EINVAL', $ex);
|
|
312
323
|
if (!src.stats)
|
|
313
324
|
throw UV('ENOENT', $ex);
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
const
|
|
325
|
+
const srcDir = dirname(src.path);
|
|
326
|
+
const dstDir = dirname(dst.path);
|
|
327
|
+
const oldParent = src.cache.statSync(srcDir, $ex);
|
|
328
|
+
const newParent = src.cache.statSync(dstDir, $ex);
|
|
317
329
|
let newStats;
|
|
318
330
|
try {
|
|
319
|
-
newStats =
|
|
331
|
+
newStats = src.cache.statSync(dst.path, $ex);
|
|
320
332
|
}
|
|
321
333
|
catch (e) {
|
|
322
334
|
if (e.code != 'ENOENT')
|
|
323
335
|
throw e;
|
|
324
336
|
}
|
|
325
|
-
|
|
337
|
+
assertRemovable(this, oldParent, src.stats, $ex);
|
|
338
|
+
if (newStats)
|
|
339
|
+
assertRemovable(this, newParent, newStats, $ex);
|
|
340
|
+
if (checkAccess && !hasAccess(this, newParent, constants.W_OK | constants.X_OK))
|
|
341
|
+
throw UV('EACCES', $ex);
|
|
342
|
+
if (checkAccess && isDirectory(src.stats) && srcDir != dstDir && !hasAccess(this, src.stats, constants.W_OK))
|
|
326
343
|
throw UV('EACCES', $ex);
|
|
327
344
|
if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
|
|
328
345
|
throw UV('EISDIR', $ex);
|
|
329
346
|
if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
|
|
330
347
|
throw UV('ENOTDIR', $ex);
|
|
331
|
-
const srcDir = dirname(src.path);
|
|
332
|
-
const dstDir = dirname(dst.path);
|
|
333
348
|
// Lock both parent directories, ordered by inode number to avoid ABBA deadlocks (like Linux's `lock_two_nondirectories`)
|
|
334
349
|
const parents = [
|
|
335
350
|
[srcDir, oldParent],
|
|
@@ -337,10 +352,10 @@ export function rename(oldPath, newPath) {
|
|
|
337
352
|
];
|
|
338
353
|
if (oldParent.ino > newParent.ino)
|
|
339
354
|
parents.reverse();
|
|
340
|
-
const _first = __addDisposableResource(env_4,
|
|
341
|
-
const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null :
|
|
355
|
+
const _first = __addDisposableResource(env_4, src.cache.lockSync(parents[0][0], 'rw', parents[0][1]), false);
|
|
356
|
+
const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : src.cache.lockSync(parents[1][0], 'rw', parents[1][1]), false);
|
|
342
357
|
src.fs.renameSync(src.path, dst.path);
|
|
343
|
-
|
|
358
|
+
src.cache.rename(src.path, dst.path);
|
|
344
359
|
// Both names change which entries exist, so both are 'rename' events
|
|
345
360
|
emitChange(this, 'rename', oldPath);
|
|
346
361
|
emitChange(this, 'rename', newPath);
|
|
@@ -359,24 +374,24 @@ export function link(target, link) {
|
|
|
359
374
|
target = normalizePath.call(this, target);
|
|
360
375
|
link = normalizePath.call(this, link);
|
|
361
376
|
const $ex = { syscall: 'link', path: target, dest: link };
|
|
362
|
-
const { fs, path: resolved } =
|
|
363
|
-
const dst =
|
|
377
|
+
const { fs, cache, path: resolved } = resolve(this, target, true, $ex);
|
|
378
|
+
const dst = resolve(this, link, true, $ex);
|
|
364
379
|
if (fs.uuid !== dst.fs.uuid)
|
|
365
380
|
throw UV('EXDEV', $ex);
|
|
366
|
-
const stats =
|
|
381
|
+
const stats = cache.statSync(resolved, $ex);
|
|
367
382
|
if (checkAccess) {
|
|
368
383
|
if (!hasAccess(this, stats, constants.R_OK))
|
|
369
384
|
throw UV('EACCES', $ex);
|
|
370
|
-
const dirStats =
|
|
385
|
+
const dirStats = cache.statSync(dirname(resolved), $ex);
|
|
371
386
|
if (!hasAccess(this, dirStats, constants.R_OK))
|
|
372
387
|
throw UV('EACCES', $ex);
|
|
373
|
-
const destStats =
|
|
388
|
+
const destStats = cache.statSync(dirname(dst.path), $ex);
|
|
374
389
|
if (!hasAccess(this, destStats, constants.W_OK))
|
|
375
390
|
throw UV('EACCES', $ex);
|
|
376
391
|
}
|
|
377
|
-
const _ = __addDisposableResource(env_5,
|
|
392
|
+
const _ = __addDisposableResource(env_5, cache.lockSync(dirname(dst.path), 'rw'), false);
|
|
378
393
|
fs.linkSync(resolved, dst.path);
|
|
379
|
-
|
|
394
|
+
cache.link(resolved, dst.path);
|
|
380
395
|
}
|
|
381
396
|
catch (e_5) {
|
|
382
397
|
env_5.error = e_5;
|
|
@@ -394,10 +409,15 @@ export function stat(path, lstat) {
|
|
|
394
409
|
stats = resolve(this, path, false, extra).stats;
|
|
395
410
|
else {
|
|
396
411
|
const { base, dir } = parse(path);
|
|
397
|
-
const
|
|
412
|
+
const parent = resolve(this, dir, false, extra);
|
|
413
|
+
if (parent.stats && isDirectory(parent.stats))
|
|
414
|
+
assertSearchable(this, parent.stats, extra);
|
|
415
|
+
const { root, mounts } = contextOf(this);
|
|
416
|
+
const mounted = base && mounts.get(join(root, parent.fullPath, base));
|
|
417
|
+
const fs = mounted || parent.fs;
|
|
418
|
+
const target = mounted ? '/' : join(parent.path, base);
|
|
398
419
|
try {
|
|
399
|
-
|
|
400
|
-
stats = cacheOf(fs).get(target)?.inode ?? fs.statSync(target);
|
|
420
|
+
stats = cacheOf(fs).statSync(target);
|
|
401
421
|
}
|
|
402
422
|
catch (e) {
|
|
403
423
|
setUVMessage(Object.assign(e, extra));
|
|
@@ -406,7 +426,65 @@ export function stat(path, lstat) {
|
|
|
406
426
|
}
|
|
407
427
|
if (!stats)
|
|
408
428
|
throw UV('ENOENT', extra);
|
|
409
|
-
if (checkAccess && !hasAccess(this, stats, constants.R_OK))
|
|
410
|
-
throw UV('EACCES', extra);
|
|
411
429
|
return stats;
|
|
412
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Opens a handle for changing metadata, which does not need read or write access to the file.
|
|
433
|
+
*/
|
|
434
|
+
function _openMetadata($, path, syscall, noFollow) {
|
|
435
|
+
const { fullPath, cache, path: resolved, stats } = resolve($, path, noFollow, { syscall });
|
|
436
|
+
if (!stats)
|
|
437
|
+
throw UV('ENOENT', syscall, path);
|
|
438
|
+
return new Handle($, fullPath, resolved, constants.O_RDONLY, cache.ref(resolved, stats));
|
|
439
|
+
}
|
|
440
|
+
export function chmod(path, mode, noFollow) {
|
|
441
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
442
|
+
try {
|
|
443
|
+
path = normalizePath.call(this, path);
|
|
444
|
+
const numMode = normalizeMode(mode, -1);
|
|
445
|
+
if (numMode < 0)
|
|
446
|
+
throw UV('EINVAL', 'chmod', path);
|
|
447
|
+
const handle = __addDisposableResource(env_6, _openMetadata(this, path, 'chmod', noFollow), false);
|
|
448
|
+
handle.chmodSync(numMode);
|
|
449
|
+
emitChange(this, 'change', handle.path);
|
|
450
|
+
}
|
|
451
|
+
catch (e_6) {
|
|
452
|
+
env_6.error = e_6;
|
|
453
|
+
env_6.hasError = true;
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
__disposeResources(env_6);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
export function chown(path, uid, gid, noFollow) {
|
|
460
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
461
|
+
try {
|
|
462
|
+
path = normalizePath.call(this, path);
|
|
463
|
+
const handle = __addDisposableResource(env_7, _openMetadata(this, path, 'chown', noFollow), false);
|
|
464
|
+
handle.chownSync(uid, gid);
|
|
465
|
+
emitChange(this, 'change', handle.path);
|
|
466
|
+
}
|
|
467
|
+
catch (e_7) {
|
|
468
|
+
env_7.error = e_7;
|
|
469
|
+
env_7.hasError = true;
|
|
470
|
+
}
|
|
471
|
+
finally {
|
|
472
|
+
__disposeResources(env_7);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
export function utimes(path, atime, mtime, noFollow) {
|
|
476
|
+
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
477
|
+
try {
|
|
478
|
+
path = normalizePath.call(this, path);
|
|
479
|
+
const handle = __addDisposableResource(env_8, _openMetadata(this, path, 'utimes', noFollow), false);
|
|
480
|
+
handle.utimesSync(normalizeTime(atime), normalizeTime(mtime));
|
|
481
|
+
emitChange(this, 'change', handle.path);
|
|
482
|
+
}
|
|
483
|
+
catch (e_8) {
|
|
484
|
+
env_8.error = e_8;
|
|
485
|
+
env_8.hasError = true;
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
__disposeResources(env_8);
|
|
489
|
+
}
|
|
490
|
+
}
|
package/dist/vfs/vcache.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { LockMode, LockRelease } from 'kerium/locks';
|
|
2
|
+
import { type ExceptionExtra } from 'kerium';
|
|
2
3
|
import type { FileSystem } from '../internal/filesystem.js';
|
|
3
4
|
import type { InodeLike } from '../internal/inode.js';
|
|
5
|
+
import '../polyfills.js';
|
|
4
6
|
import { VNode } from './vnode.js';
|
|
5
7
|
/**
|
|
6
8
|
* The VFS-level cache of vnodes for a single file system. Modeled after Linux's dcache/icache.
|
|
@@ -35,6 +37,18 @@ export declare class VCache {
|
|
|
35
37
|
sync(): Promise<void>;
|
|
36
38
|
/** Sync all dirty vnodes to the backend synchronously */
|
|
37
39
|
syncSync(): void;
|
|
40
|
+
/** Get the inode for a path, preferring the cached vnode since it may have unsynced changes */
|
|
41
|
+
stat(path: string, extra?: ExceptionExtra): Promise<InodeLike>;
|
|
42
|
+
/** Synchronous version of {@link stat} */
|
|
43
|
+
statSync(path: string, extra?: ExceptionExtra): InodeLike;
|
|
44
|
+
/**
|
|
45
|
+
* Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
|
|
46
|
+
* The returned release function unlocks and unrefs the vnode.
|
|
47
|
+
* If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
|
|
48
|
+
*/
|
|
49
|
+
lock(path: string, mode: LockMode, inode?: InodeLike): Promise<LockRelease>;
|
|
50
|
+
/** Synchronous version of {@link lock} */
|
|
51
|
+
lockSync(path: string, mode: LockMode, inode?: InodeLike): LockRelease;
|
|
38
52
|
}
|
|
39
53
|
/**
|
|
40
54
|
* All vnode caches, keyed by file system UUID.
|
|
@@ -50,17 +64,3 @@ export declare const caches: Map<`${string}-${string}-${string}-${string}-${stri
|
|
|
50
64
|
* @internal
|
|
51
65
|
*/
|
|
52
66
|
export declare function cacheOf(fs: FileSystem): VCache;
|
|
53
|
-
/**
|
|
54
|
-
* Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
|
|
55
|
-
* The returned release function unlocks and unrefs the vnode.
|
|
56
|
-
* If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
|
|
57
|
-
* @category VFS
|
|
58
|
-
* @internal
|
|
59
|
-
*/
|
|
60
|
-
export declare function lockPath(fs: FileSystem, path: string, mode: LockMode, inode?: InodeLike): Promise<LockRelease>;
|
|
61
|
-
/**
|
|
62
|
-
* Synchronous version of `lockPath`.
|
|
63
|
-
* @category VFS
|
|
64
|
-
* @internal
|
|
65
|
-
*/
|
|
66
|
-
export declare function lockPathSync(fs: FileSystem, path: string, mode: LockMode, inode?: InodeLike): LockRelease;
|
package/dist/vfs/vcache.js
CHANGED
|
@@ -50,7 +50,10 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
+
import { setUVMessage } from 'kerium';
|
|
53
54
|
import { err } from 'kerium/log';
|
|
55
|
+
import { withoutExceptionContext } from '../internal/error.js';
|
|
56
|
+
import '../polyfills.js';
|
|
54
57
|
import { VNode } from './vnode.js';
|
|
55
58
|
/**
|
|
56
59
|
* The VFS-level cache of vnodes for a single file system. Modeled after Linux's dcache/icache.
|
|
@@ -176,6 +179,67 @@ export class VCache {
|
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
181
|
}
|
|
182
|
+
/** Get the inode for a path, preferring the cached vnode since it may have unsynced changes */
|
|
183
|
+
async stat(path, extra) {
|
|
184
|
+
try {
|
|
185
|
+
return this.get(path)?.inode ?? (await this.fs.stat(path));
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
throw extra ? setUVMessage(Object.assign(e, extra)) : e;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** Synchronous version of {@link stat} */
|
|
192
|
+
statSync(path, extra) {
|
|
193
|
+
try {
|
|
194
|
+
return this.get(path)?.inode ?? this.fs.statSync(path);
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
throw extra ? setUVMessage(Object.assign(e, extra)) : e;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
|
|
202
|
+
* The returned release function unlocks and unrefs the vnode.
|
|
203
|
+
* If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
|
|
204
|
+
*/
|
|
205
|
+
async lock(path, mode, inode) {
|
|
206
|
+
inode ??= await this.stat(path);
|
|
207
|
+
const node = this.ref(path, inode);
|
|
208
|
+
const unlock = await node.lock(mode);
|
|
209
|
+
let released = false;
|
|
210
|
+
const release = () => {
|
|
211
|
+
if (released)
|
|
212
|
+
return;
|
|
213
|
+
released = true;
|
|
214
|
+
unlock();
|
|
215
|
+
this.unref(node);
|
|
216
|
+
};
|
|
217
|
+
release[Symbol.dispose] = release;
|
|
218
|
+
return release;
|
|
219
|
+
}
|
|
220
|
+
/** Synchronous version of {@link lock} */
|
|
221
|
+
lockSync(path, mode, inode) {
|
|
222
|
+
inode ??= this.statSync(path);
|
|
223
|
+
const node = this.ref(path, inode);
|
|
224
|
+
let unlock;
|
|
225
|
+
try {
|
|
226
|
+
unlock = node.lockSync(mode);
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
this.unref(node);
|
|
230
|
+
throw e;
|
|
231
|
+
}
|
|
232
|
+
let released = false;
|
|
233
|
+
const release = () => {
|
|
234
|
+
if (released)
|
|
235
|
+
return;
|
|
236
|
+
released = true;
|
|
237
|
+
unlock();
|
|
238
|
+
this.unref(node);
|
|
239
|
+
};
|
|
240
|
+
release[Symbol.dispose] = release;
|
|
241
|
+
return release;
|
|
242
|
+
}
|
|
179
243
|
}
|
|
180
244
|
/**
|
|
181
245
|
* All vnode caches, keyed by file system UUID.
|
|
@@ -191,61 +255,5 @@ export const caches = new Map();
|
|
|
191
255
|
* @internal
|
|
192
256
|
*/
|
|
193
257
|
export function cacheOf(fs) {
|
|
194
|
-
|
|
195
|
-
if (!cache) {
|
|
196
|
-
cache = new VCache(fs);
|
|
197
|
-
caches.set(fs.uuid, cache);
|
|
198
|
-
}
|
|
199
|
-
return cache;
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
|
|
203
|
-
* The returned release function unlocks and unrefs the vnode.
|
|
204
|
-
* If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
|
|
205
|
-
* @category VFS
|
|
206
|
-
* @internal
|
|
207
|
-
*/
|
|
208
|
-
export async function lockPath(fs, path, mode, inode) {
|
|
209
|
-
const cache = cacheOf(fs);
|
|
210
|
-
inode ??= cache.get(path)?.inode ?? (await fs.stat(path));
|
|
211
|
-
const node = cache.ref(path, inode);
|
|
212
|
-
const unlock = await node.lock(mode);
|
|
213
|
-
let released = false;
|
|
214
|
-
const release = () => {
|
|
215
|
-
if (released)
|
|
216
|
-
return;
|
|
217
|
-
released = true;
|
|
218
|
-
unlock();
|
|
219
|
-
cache.unref(node);
|
|
220
|
-
};
|
|
221
|
-
release[Symbol.dispose] = release;
|
|
222
|
-
return release;
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Synchronous version of `lockPath`.
|
|
226
|
-
* @category VFS
|
|
227
|
-
* @internal
|
|
228
|
-
*/
|
|
229
|
-
export function lockPathSync(fs, path, mode, inode) {
|
|
230
|
-
const cache = cacheOf(fs);
|
|
231
|
-
inode ??= cache.get(path)?.inode ?? fs.statSync(path);
|
|
232
|
-
const node = cache.ref(path, inode);
|
|
233
|
-
let unlock;
|
|
234
|
-
try {
|
|
235
|
-
unlock = node.lockSync(mode);
|
|
236
|
-
}
|
|
237
|
-
catch (e) {
|
|
238
|
-
cache.unref(node);
|
|
239
|
-
throw e;
|
|
240
|
-
}
|
|
241
|
-
let released = false;
|
|
242
|
-
const release = () => {
|
|
243
|
-
if (released)
|
|
244
|
-
return;
|
|
245
|
-
released = true;
|
|
246
|
-
unlock();
|
|
247
|
-
cache.unref(node);
|
|
248
|
-
};
|
|
249
|
-
release[Symbol.dispose] = release;
|
|
250
|
-
return release;
|
|
258
|
+
return caches.getOrInsertComputed(fs.uuid, () => new VCache(withoutExceptionContext(fs)));
|
|
251
259
|
}
|
package/dist/vfs/watchers.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type * as fs from 'node:fs';
|
|
|
3
3
|
import type { V_Context } from '../context.js';
|
|
4
4
|
import { EventEmitter } from 'eventemitter3';
|
|
5
5
|
import { type Stats } from '../node/stats.js';
|
|
6
|
+
import '../polyfills.js';
|
|
6
7
|
/**
|
|
7
8
|
* Base class for file system watchers.
|
|
8
9
|
* Provides event handling capabilities for watching file system changes.
|
package/dist/vfs/watchers.js
CHANGED
|
@@ -4,6 +4,7 @@ import { contextOf } from '../internal/contexts.js';
|
|
|
4
4
|
import { isStatsEqual } from '../node/stats.js';
|
|
5
5
|
import { statSync } from '../node/sync.js';
|
|
6
6
|
import { basename, dirname, join, relative } from '../path.js';
|
|
7
|
+
import '../polyfills.js';
|
|
7
8
|
import { normalizePath } from '../utils.js';
|
|
8
9
|
/** Alias so `.call` resolves to the non-bigint overload */
|
|
9
10
|
const _statSync = statSync;
|
|
@@ -130,10 +131,7 @@ export class StatWatcher extends Watcher {
|
|
|
130
131
|
const watchers = new Map();
|
|
131
132
|
export function addWatcher(path, watcher) {
|
|
132
133
|
const normalizedPath = normalizePath(path);
|
|
133
|
-
|
|
134
|
-
watchers.set(normalizedPath, new Set());
|
|
135
|
-
}
|
|
136
|
-
watchers.get(normalizedPath).add(watcher);
|
|
134
|
+
watchers.getOrInsertComputed(normalizedPath, () => new Set()).add(watcher);
|
|
137
135
|
}
|
|
138
136
|
export function removeWatcher(path, watcher) {
|
|
139
137
|
const normalizedPath = normalizePath(path);
|