@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/sync.js
CHANGED
|
@@ -55,11 +55,11 @@ import { decodeUTF8 } from 'utilium';
|
|
|
55
55
|
import * as constants from '../constants.js';
|
|
56
56
|
import { 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
60
|
import { normalizeMode, normalizePath } from '../utils.js';
|
|
61
|
-
import { cacheOf
|
|
62
|
-
import { checkAccess } from './config.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);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Go the long way
|
|
84
91
|
}
|
|
85
|
-
|
|
86
|
-
|
|
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,7 +153,7 @@ 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
159
|
mode,
|
|
@@ -152,7 +162,7 @@ export function open($, path, opt) {
|
|
|
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,7 +202,7 @@ 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
208
|
const { mode = 0o777, recursive } = options;
|
|
@@ -201,11 +211,11 @@ export function mkdir(path, options = {}) {
|
|
|
201
211
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
202
212
|
try {
|
|
203
213
|
const parentPath = dirname(resolved);
|
|
204
|
-
const parent = recursive && parentPath != '/' ? __create(dirname(path), parentPath) :
|
|
214
|
+
const parent = recursive && parentPath != '/' ? __create(dirname(path), parentPath) : cache.statSync(parentPath, $ex);
|
|
205
215
|
if (recursive) {
|
|
206
216
|
let existing;
|
|
207
217
|
try {
|
|
208
|
-
existing =
|
|
218
|
+
existing = cache.statSync(resolved);
|
|
209
219
|
}
|
|
210
220
|
catch {
|
|
211
221
|
// It doesn't exist yet
|
|
@@ -221,9 +231,9 @@ export function mkdir(path, options = {}) {
|
|
|
221
231
|
if (followed && fs.existsSync(join(parentPath, basename(path))))
|
|
222
232
|
throw UV('ENOENT', $ex);
|
|
223
233
|
}
|
|
224
|
-
if (checkAccess && !hasAccess(this, parent, constants.W_OK))
|
|
234
|
+
if (checkAccess && !hasAccess(this, parent, constants.W_OK | constants.X_OK))
|
|
225
235
|
throw UV('EACCES', 'mkdir', path);
|
|
226
|
-
const _ = __addDisposableResource(env_2,
|
|
236
|
+
const _ = __addDisposableResource(env_2, cache.lockSync(parentPath, 'rw', parent), false);
|
|
227
237
|
const inode = wrap(fs, 'mkdirSync', { path, syscall: 'mkdir' })(resolved, {
|
|
228
238
|
mode,
|
|
229
239
|
uid: parent.mode & constants.S_ISUID ? parent.uid : uid,
|
|
@@ -247,9 +257,9 @@ export function mkdir(path, options = {}) {
|
|
|
247
257
|
}
|
|
248
258
|
export function readdir(path, options = {}) {
|
|
249
259
|
path = normalizePath.call(this, path);
|
|
250
|
-
const { fs, path: resolved } = resolve(this, path);
|
|
260
|
+
const { fs, cache, path: resolved } = resolve(this, path);
|
|
251
261
|
// Node reports `readdir` failures as `scandir`
|
|
252
|
-
const stats =
|
|
262
|
+
const stats = cache.statSync(resolved, { path, syscall: 'scandir' });
|
|
253
263
|
if (checkAccess && !hasAccess(this, stats, constants.R_OK))
|
|
254
264
|
throw UV('EACCES', 'scandir', path);
|
|
255
265
|
if (!isDirectory(stats))
|
|
@@ -258,7 +268,7 @@ export function readdir(path, options = {}) {
|
|
|
258
268
|
{
|
|
259
269
|
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
260
270
|
try {
|
|
261
|
-
const _ = __addDisposableResource(env_3,
|
|
271
|
+
const _ = __addDisposableResource(env_3, cache.lockSync(resolved, 'ro', stats), false);
|
|
262
272
|
entries = fs.readdirSync(resolved);
|
|
263
273
|
}
|
|
264
274
|
catch (e_3) {
|
|
@@ -269,12 +279,11 @@ export function readdir(path, options = {}) {
|
|
|
269
279
|
__disposeResources(env_3);
|
|
270
280
|
}
|
|
271
281
|
}
|
|
272
|
-
// Iterate over entries and handle recursive case if needed
|
|
273
282
|
const values = [];
|
|
274
283
|
const addEntry = (entry) => {
|
|
275
284
|
let entryStat;
|
|
276
285
|
try {
|
|
277
|
-
entryStat =
|
|
286
|
+
entryStat = cache.statSync(join(resolved, entry), { syscall: 'scandir', path });
|
|
278
287
|
}
|
|
279
288
|
catch (e) {
|
|
280
289
|
if (e.code == 'ENOENT')
|
|
@@ -304,7 +313,7 @@ export function rename(oldPath, newPath) {
|
|
|
304
313
|
newPath = normalizePath.call(this, newPath);
|
|
305
314
|
const $ex = { syscall: 'rename', path: oldPath, dest: newPath };
|
|
306
315
|
const src = resolve(this, oldPath, true, $ex);
|
|
307
|
-
const dst =
|
|
316
|
+
const dst = resolve(this, newPath, true, $ex);
|
|
308
317
|
if (src.fs.uuid !== dst.fs.uuid)
|
|
309
318
|
throw UV('EXDEV', $ex);
|
|
310
319
|
// A directory can not be moved inside itself
|
|
@@ -312,25 +321,29 @@ export function rename(oldPath, newPath) {
|
|
|
312
321
|
throw UV('EINVAL', $ex);
|
|
313
322
|
if (!src.stats)
|
|
314
323
|
throw UV('ENOENT', $ex);
|
|
315
|
-
const
|
|
316
|
-
const
|
|
317
|
-
const
|
|
324
|
+
const srcDir = dirname(src.path);
|
|
325
|
+
const dstDir = dirname(dst.path);
|
|
326
|
+
const oldParent = src.cache.statSync(srcDir, $ex);
|
|
327
|
+
const newParent = src.cache.statSync(dstDir, $ex);
|
|
318
328
|
let newStats;
|
|
319
329
|
try {
|
|
320
|
-
newStats =
|
|
330
|
+
newStats = src.cache.statSync(dst.path, $ex);
|
|
321
331
|
}
|
|
322
332
|
catch (e) {
|
|
323
333
|
if (e.code != 'ENOENT')
|
|
324
334
|
throw e;
|
|
325
335
|
}
|
|
326
|
-
|
|
336
|
+
assertRemovable(this, oldParent, src.stats, $ex);
|
|
337
|
+
if (newStats)
|
|
338
|
+
assertRemovable(this, newParent, newStats, $ex);
|
|
339
|
+
if (checkAccess && !hasAccess(this, newParent, constants.W_OK | constants.X_OK))
|
|
340
|
+
throw UV('EACCES', $ex);
|
|
341
|
+
if (checkAccess && isDirectory(src.stats) && srcDir != dstDir && !hasAccess(this, src.stats, constants.W_OK))
|
|
327
342
|
throw UV('EACCES', $ex);
|
|
328
343
|
if (newStats && !isDirectory(src.stats) && isDirectory(newStats))
|
|
329
344
|
throw UV('EISDIR', $ex);
|
|
330
345
|
if (newStats && isDirectory(src.stats) && !isDirectory(newStats))
|
|
331
346
|
throw UV('ENOTDIR', $ex);
|
|
332
|
-
const srcDir = dirname(src.path);
|
|
333
|
-
const dstDir = dirname(dst.path);
|
|
334
347
|
// Lock both parent directories, ordered by inode number to avoid ABBA deadlocks (like Linux's `lock_two_nondirectories`)
|
|
335
348
|
const parents = [
|
|
336
349
|
[srcDir, oldParent],
|
|
@@ -338,10 +351,10 @@ export function rename(oldPath, newPath) {
|
|
|
338
351
|
];
|
|
339
352
|
if (oldParent.ino > newParent.ino)
|
|
340
353
|
parents.reverse();
|
|
341
|
-
const _first = __addDisposableResource(env_4,
|
|
342
|
-
const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null :
|
|
354
|
+
const _first = __addDisposableResource(env_4, src.cache.lockSync(parents[0][0], 'rw', parents[0][1]), false);
|
|
355
|
+
const _second = __addDisposableResource(env_4, oldParent.ino == newParent.ino ? null : src.cache.lockSync(parents[1][0], 'rw', parents[1][1]), false);
|
|
343
356
|
src.fs.renameSync(src.path, dst.path);
|
|
344
|
-
|
|
357
|
+
src.cache.rename(src.path, dst.path);
|
|
345
358
|
// Both names change which entries exist, so both are 'rename' events
|
|
346
359
|
emitChange(this, 'rename', oldPath);
|
|
347
360
|
emitChange(this, 'rename', newPath);
|
|
@@ -360,24 +373,24 @@ export function link(target, link) {
|
|
|
360
373
|
target = normalizePath.call(this, target);
|
|
361
374
|
link = normalizePath.call(this, link);
|
|
362
375
|
const $ex = { syscall: 'link', path: target, dest: link };
|
|
363
|
-
const { fs, path: resolved } =
|
|
364
|
-
const dst =
|
|
376
|
+
const { fs, cache, path: resolved } = resolve(this, target, true, $ex);
|
|
377
|
+
const dst = resolve(this, link, true, $ex);
|
|
365
378
|
if (fs.uuid !== dst.fs.uuid)
|
|
366
379
|
throw UV('EXDEV', $ex);
|
|
367
|
-
const stats =
|
|
380
|
+
const stats = cache.statSync(resolved, $ex);
|
|
368
381
|
if (checkAccess) {
|
|
369
382
|
if (!hasAccess(this, stats, constants.R_OK))
|
|
370
383
|
throw UV('EACCES', $ex);
|
|
371
|
-
const dirStats =
|
|
384
|
+
const dirStats = cache.statSync(dirname(resolved), $ex);
|
|
372
385
|
if (!hasAccess(this, dirStats, constants.R_OK))
|
|
373
386
|
throw UV('EACCES', $ex);
|
|
374
|
-
const destStats =
|
|
387
|
+
const destStats = cache.statSync(dirname(dst.path), $ex);
|
|
375
388
|
if (!hasAccess(this, destStats, constants.W_OK))
|
|
376
389
|
throw UV('EACCES', $ex);
|
|
377
390
|
}
|
|
378
|
-
const _ = __addDisposableResource(env_5,
|
|
391
|
+
const _ = __addDisposableResource(env_5, cache.lockSync(dirname(dst.path), 'rw'), false);
|
|
379
392
|
fs.linkSync(resolved, dst.path);
|
|
380
|
-
|
|
393
|
+
cache.link(resolved, dst.path);
|
|
381
394
|
}
|
|
382
395
|
catch (e_5) {
|
|
383
396
|
env_5.error = e_5;
|
|
@@ -395,10 +408,15 @@ export function stat(path, lstat) {
|
|
|
395
408
|
stats = resolve(this, path, false, extra).stats;
|
|
396
409
|
else {
|
|
397
410
|
const { base, dir } = parse(path);
|
|
398
|
-
const
|
|
411
|
+
const parent = resolve(this, dir, false, extra);
|
|
412
|
+
if (parent.stats && isDirectory(parent.stats))
|
|
413
|
+
assertSearchable(this, parent.stats, extra);
|
|
414
|
+
const { root, mounts } = contextOf(this);
|
|
415
|
+
const mounted = base && mounts.get(join(root, parent.fullPath, base));
|
|
416
|
+
const fs = mounted || parent.fs;
|
|
417
|
+
const target = mounted ? '/' : join(parent.path, base);
|
|
399
418
|
try {
|
|
400
|
-
|
|
401
|
-
stats = cacheOf(fs).get(target)?.inode ?? fs.statSync(target);
|
|
419
|
+
stats = cacheOf(fs).statSync(target);
|
|
402
420
|
}
|
|
403
421
|
catch (e) {
|
|
404
422
|
setUVMessage(Object.assign(e, extra));
|
|
@@ -407,7 +425,5 @@ export function stat(path, lstat) {
|
|
|
407
425
|
}
|
|
408
426
|
if (!stats)
|
|
409
427
|
throw UV('ENOENT', extra);
|
|
410
|
-
if (checkAccess && !hasAccess(this, stats, constants.R_OK))
|
|
411
|
-
throw UV('EACCES', extra);
|
|
412
428
|
return stats;
|
|
413
429
|
}
|
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.
|
|
@@ -85,7 +88,6 @@ export class VCache {
|
|
|
85
88
|
}
|
|
86
89
|
else {
|
|
87
90
|
node.paths.add(path);
|
|
88
|
-
// The vnode's inode is authoritative when dirty; otherwise refresh it with the newer stats
|
|
89
91
|
if (!node.dirty && inode !== node.inode)
|
|
90
92
|
Object.assign(node.inode, inode);
|
|
91
93
|
}
|
|
@@ -177,6 +179,67 @@ export class VCache {
|
|
|
177
179
|
}
|
|
178
180
|
}
|
|
179
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
|
+
}
|
|
180
243
|
}
|
|
181
244
|
/**
|
|
182
245
|
* All vnode caches, keyed by file system UUID.
|
|
@@ -192,61 +255,5 @@ export const caches = new Map();
|
|
|
192
255
|
* @internal
|
|
193
256
|
*/
|
|
194
257
|
export function cacheOf(fs) {
|
|
195
|
-
|
|
196
|
-
if (!cache) {
|
|
197
|
-
cache = new VCache(fs);
|
|
198
|
-
caches.set(fs.uuid, cache);
|
|
199
|
-
}
|
|
200
|
-
return cache;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Ref and lock the vnode for a path, usually a parent directory during a namespace operation.
|
|
204
|
-
* The returned release function unlocks and unrefs the vnode.
|
|
205
|
-
* If `inode` is not provided and the vnode is not cached, the backend is `stat`ed.
|
|
206
|
-
* @category VFS
|
|
207
|
-
* @internal
|
|
208
|
-
*/
|
|
209
|
-
export async function lockPath(fs, path, mode, inode) {
|
|
210
|
-
const cache = cacheOf(fs);
|
|
211
|
-
inode ??= cache.get(path)?.inode ?? (await fs.stat(path));
|
|
212
|
-
const node = cache.ref(path, inode);
|
|
213
|
-
const unlock = await node.lock(mode);
|
|
214
|
-
let released = false;
|
|
215
|
-
const release = () => {
|
|
216
|
-
if (released)
|
|
217
|
-
return;
|
|
218
|
-
released = true;
|
|
219
|
-
unlock();
|
|
220
|
-
cache.unref(node);
|
|
221
|
-
};
|
|
222
|
-
release[Symbol.dispose] = release;
|
|
223
|
-
return release;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Synchronous version of `lockPath`.
|
|
227
|
-
* @category VFS
|
|
228
|
-
* @internal
|
|
229
|
-
*/
|
|
230
|
-
export function lockPathSync(fs, path, mode, inode) {
|
|
231
|
-
const cache = cacheOf(fs);
|
|
232
|
-
inode ??= cache.get(path)?.inode ?? fs.statSync(path);
|
|
233
|
-
const node = cache.ref(path, inode);
|
|
234
|
-
let unlock;
|
|
235
|
-
try {
|
|
236
|
-
unlock = node.lockSync(mode);
|
|
237
|
-
}
|
|
238
|
-
catch (e) {
|
|
239
|
-
cache.unref(node);
|
|
240
|
-
throw e;
|
|
241
|
-
}
|
|
242
|
-
let released = false;
|
|
243
|
-
const release = () => {
|
|
244
|
-
if (released)
|
|
245
|
-
return;
|
|
246
|
-
released = true;
|
|
247
|
-
unlock();
|
|
248
|
-
cache.unref(node);
|
|
249
|
-
};
|
|
250
|
-
release[Symbol.dispose] = release;
|
|
251
|
-
return release;
|
|
258
|
+
return caches.getOrInsertComputed(fs.uuid, () => new VCache(withoutExceptionContext(fs)));
|
|
252
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);
|
package/dist/vfs/xattr.js
CHANGED
|
@@ -5,7 +5,8 @@ import { Attributes, hasAccess } from '../internal/inode.js';
|
|
|
5
5
|
import { normalizePath } from '../utils.js';
|
|
6
6
|
import { checkAccess } from './config.js';
|
|
7
7
|
import { R_OK, W_OK } from '../constants.js';
|
|
8
|
-
import {
|
|
8
|
+
import { resolve as resolveAsync } from './async.js';
|
|
9
|
+
import { resolve as resolveSync } from './sync.js';
|
|
9
10
|
/** Attributes outside the `user` namespace that are supported. */
|
|
10
11
|
export const allowedRestrictedNames = new Set();
|
|
11
12
|
/**
|
|
@@ -19,9 +20,9 @@ function checkName($, name, path, syscall) {
|
|
|
19
20
|
}
|
|
20
21
|
export async function get(path, name, opt = {}) {
|
|
21
22
|
path = normalizePath.call(this, path);
|
|
22
|
-
const {
|
|
23
|
+
const { cache, path: resolved } = await resolveAsync(this, path, opt.noFollow);
|
|
23
24
|
checkName(this, name, path, 'xattr.get');
|
|
24
|
-
const inode = await
|
|
25
|
+
const inode = await cache.stat(resolved).catch(rethrow('xattr.get', path));
|
|
25
26
|
if (checkAccess && !hasAccess(this, inode, R_OK))
|
|
26
27
|
throw UV('EACCES', 'xattr.get', path);
|
|
27
28
|
inode.attributes ??= new Attributes();
|
|
@@ -34,10 +35,10 @@ export async function get(path, name, opt = {}) {
|
|
|
34
35
|
export function getSync(path, name, opt = {}) {
|
|
35
36
|
path = normalizePath.call(this, path);
|
|
36
37
|
checkName(this, name, path, 'xattr.get');
|
|
37
|
-
const {
|
|
38
|
+
const { cache, path: resolved } = resolveSync(this, path, opt.noFollow);
|
|
38
39
|
let inode;
|
|
39
40
|
try {
|
|
40
|
-
inode =
|
|
41
|
+
inode = cache.statSync(resolved);
|
|
41
42
|
}
|
|
42
43
|
catch (e) {
|
|
43
44
|
throw setUVMessage(Object.assign(e, { path }));
|
|
@@ -61,9 +62,9 @@ export function getSync(path, name, opt = {}) {
|
|
|
61
62
|
*/
|
|
62
63
|
export async function set(path, name, value, opt = {}) {
|
|
63
64
|
path = normalizePath.call(this, path);
|
|
64
|
-
const { fs, path: resolved } =
|
|
65
|
+
const { fs, cache, path: resolved } = await resolveAsync(this, path, opt.noFollow);
|
|
65
66
|
checkName(this, name, path, 'xattr.set');
|
|
66
|
-
const inode = await
|
|
67
|
+
const inode = await cache.stat(resolved).catch(rethrow('xattr.set', path));
|
|
67
68
|
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
68
69
|
throw UV('EACCES', 'xattr.set', path);
|
|
69
70
|
inode.attributes ??= new Attributes();
|
|
@@ -85,11 +86,11 @@ export async function set(path, name, value, opt = {}) {
|
|
|
85
86
|
*/
|
|
86
87
|
export function setSync(path, name, value, opt = {}) {
|
|
87
88
|
path = normalizePath.call(this, path);
|
|
88
|
-
const { fs, path: resolved } =
|
|
89
|
+
const { fs, cache, path: resolved } = resolveSync(this, path, opt.noFollow);
|
|
89
90
|
checkName(this, name, path, 'xattr.set');
|
|
90
91
|
let inode;
|
|
91
92
|
try {
|
|
92
|
-
inode =
|
|
93
|
+
inode = cache.statSync(resolved);
|
|
93
94
|
}
|
|
94
95
|
catch (e) {
|
|
95
96
|
throw setUVMessage(Object.assign(e, { path }));
|
|
@@ -118,9 +119,9 @@ export function setSync(path, name, value, opt = {}) {
|
|
|
118
119
|
*/
|
|
119
120
|
export async function remove(path, name) {
|
|
120
121
|
path = normalizePath.call(this, path);
|
|
121
|
-
const { fs, path: resolved } =
|
|
122
|
+
const { fs, cache, path: resolved } = await resolveAsync(this, path);
|
|
122
123
|
checkName(this, name, path, 'xattr.remove');
|
|
123
|
-
const inode = await
|
|
124
|
+
const inode = await cache.stat(resolved).catch(rethrow('xattr.remove', path));
|
|
124
125
|
if (checkAccess && !hasAccess(this, inode, W_OK))
|
|
125
126
|
throw UV('EACCES', 'xattr.remove', path);
|
|
126
127
|
inode.attributes ??= new Attributes();
|
|
@@ -138,11 +139,11 @@ export async function remove(path, name) {
|
|
|
138
139
|
*/
|
|
139
140
|
export function removeSync(path, name) {
|
|
140
141
|
path = normalizePath.call(this, path);
|
|
141
|
-
const { fs, path: resolved } =
|
|
142
|
+
const { fs, cache, path: resolved } = resolveSync(this, path);
|
|
142
143
|
checkName(this, name, path, 'xattr.remove');
|
|
143
144
|
let inode;
|
|
144
145
|
try {
|
|
145
|
-
inode =
|
|
146
|
+
inode = cache.statSync(resolved);
|
|
146
147
|
}
|
|
147
148
|
catch (e) {
|
|
148
149
|
throw setUVMessage(Object.assign(e, { path }));
|
|
@@ -169,8 +170,8 @@ export function removeSync(path, name) {
|
|
|
169
170
|
*/
|
|
170
171
|
export async function list(path) {
|
|
171
172
|
path = normalizePath.call(this, path);
|
|
172
|
-
const {
|
|
173
|
-
const inode = await
|
|
173
|
+
const { cache, path: resolved } = await resolveAsync(this, path);
|
|
174
|
+
const inode = await cache.stat(resolved).catch(rethrow('xattr.list', path));
|
|
174
175
|
if (!inode.attributes)
|
|
175
176
|
return [];
|
|
176
177
|
return [...inode.attributes.keys()];
|
|
@@ -183,10 +184,10 @@ export async function list(path) {
|
|
|
183
184
|
*/
|
|
184
185
|
export function listSync(path) {
|
|
185
186
|
path = normalizePath.call(this, path);
|
|
186
|
-
const {
|
|
187
|
+
const { cache, path: resolved } = resolveSync(this, path);
|
|
187
188
|
let inode;
|
|
188
189
|
try {
|
|
189
|
-
inode =
|
|
190
|
+
inode = cache.statSync(resolved);
|
|
190
191
|
}
|
|
191
192
|
catch (e) {
|
|
192
193
|
throw setUVMessage(Object.assign(e, { path }));
|