@poe-platform/safe-fs 0.1.728 → 0.1.730
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 +16 -8
- package/dist/safe-fs/contracts/filesystem.d.ts +8 -0
- package/dist/safe-fs/contracts/io.js +140 -114
- package/dist/safe-fs/core.d.ts +1 -1
- package/dist/safe-fs/core.js +1 -1
- package/dist/safe-fs/fs/capabilities.js +4 -2
- package/dist/safe-fs/fs/memory/index.d.ts +36 -0
- package/dist/safe-fs/fs/memory/index.js +704 -156
- package/dist/safe-fs/fs/memory/ledger.d.ts +9 -3
- package/dist/safe-fs/fs/memory/ledger.js +37 -4
- package/dist/safe-fs/fs/mount/index.js +1 -1
- package/dist/safe-fs/fs/overlay/index.js +5 -3
- package/dist/safe-fs/fs/quota/index.js +4 -4
- package/dist/safe-fs/fs/real/index.js +28 -9
- package/dist/safe-fs/fs/s3/filesystem.js +10 -10
- package/dist/safe-fs/fs/scoped.d.ts +1 -0
- package/dist/safe-fs/fs/scoped.js +34 -3
- package/dist/safe-fs/fs/webdav/webdav.js +7 -5
- package/dist/safe-fs/node/native-seek-unavailable.d.ts +1 -0
- package/dist/safe-fs/node/native-seek-unavailable.js +5 -0
- package/dist/safe-fs/platform/browser.d.ts +8 -0
- package/dist/safe-fs/platform/browser.js +1 -0
- package/dist/safe-fs/platform/node.d.ts +8 -0
- package/dist/safe-fs/platform/node.js +1 -0
- package/package.json +6 -2
|
@@ -15,19 +15,6 @@ import { MemoryAllocation, MemoryLedger } from "./ledger.js";
|
|
|
15
15
|
import { normalizeMemoryFileSystemLimits } from "./limits.js";
|
|
16
16
|
export { defaultMemoryFileSystemLimits } from "./limits.js";
|
|
17
17
|
class MemoryFileNode {
|
|
18
|
-
type = "file";
|
|
19
|
-
revision = 0;
|
|
20
|
-
mode;
|
|
21
|
-
ino;
|
|
22
|
-
nlink = 1;
|
|
23
|
-
atimeMs;
|
|
24
|
-
mtimeMs;
|
|
25
|
-
ctimeMs;
|
|
26
|
-
birthtimeMs;
|
|
27
|
-
references = 0;
|
|
28
|
-
byteLength;
|
|
29
|
-
view;
|
|
30
|
-
allocation;
|
|
31
18
|
constructor(mode, ino, now, byteLength, allocation, view) {
|
|
32
19
|
this.mode = mode;
|
|
33
20
|
this.ino = ino;
|
|
@@ -47,17 +34,83 @@ class MemoryFileNode {
|
|
|
47
34
|
this.byteLength = value.byteLength;
|
|
48
35
|
}
|
|
49
36
|
}
|
|
37
|
+
Object.assign(MemoryFileNode.prototype, {
|
|
38
|
+
type: "file",
|
|
39
|
+
revision: 0,
|
|
40
|
+
nlink: 1,
|
|
41
|
+
references: 0,
|
|
42
|
+
});
|
|
43
|
+
const EMPTY_ALLOC_BYTES = new Uint8Array(0);
|
|
44
|
+
const SMALL_ALLOC_SLAB_SIZE = 8192;
|
|
45
|
+
let smallAllocSlab = new Uint8Array(SMALL_ALLOC_SLAB_SIZE);
|
|
46
|
+
let smallAllocOffset = 0;
|
|
47
|
+
const DUMMY_POOL_LEDGER = new MemoryLedger(normalizeMemoryFileSystemLimits({}));
|
|
48
|
+
const DUMMY_POOL_ALLOCATION = new MemoryAllocation(EMPTY_ALLOC_BYTES, DUMMY_POOL_LEDGER);
|
|
49
|
+
const sharedAllocationPool = [];
|
|
50
|
+
const sharedFileNodePool = [];
|
|
51
|
+
const sharedDirectoryNodePool = [];
|
|
52
|
+
const sharedRemovedScratch = [];
|
|
53
|
+
let sharedRemovedKeyChars = 0;
|
|
54
|
+
let sharedRemovedKeyEntries = 0;
|
|
55
|
+
function collectRemovedChild(child, name) {
|
|
56
|
+
sharedRemovedScratch.push(child);
|
|
57
|
+
sharedRemovedKeyChars += name.length;
|
|
58
|
+
sharedRemovedKeyEntries++;
|
|
59
|
+
}
|
|
60
|
+
function replenishSharedMemoryPools() {
|
|
61
|
+
while (sharedAllocationPool.length < 112) {
|
|
62
|
+
if (smallAllocOffset + 64 > SMALL_ALLOC_SLAB_SIZE) {
|
|
63
|
+
smallAllocSlab = new Uint8Array(SMALL_ALLOC_SLAB_SIZE);
|
|
64
|
+
smallAllocOffset = 0;
|
|
65
|
+
}
|
|
66
|
+
const slice = smallAllocSlab.subarray(smallAllocOffset, smallAllocOffset + 64);
|
|
67
|
+
smallAllocOffset += 64;
|
|
68
|
+
DUMMY_POOL_LEDGER.reserve(64, 0, "init", "/");
|
|
69
|
+
const alloc = new MemoryAllocation(slice, DUMMY_POOL_LEDGER);
|
|
70
|
+
alloc.release();
|
|
71
|
+
sharedAllocationPool.push(alloc);
|
|
72
|
+
}
|
|
73
|
+
while (sharedFileNodePool.length < 112) {
|
|
74
|
+
sharedFileNodePool.push(new MemoryFileNode(0, 0, 0, 0, DUMMY_POOL_ALLOCATION, undefined));
|
|
75
|
+
}
|
|
76
|
+
while (sharedDirectoryNodePool.length < 16) {
|
|
77
|
+
sharedDirectoryNodePool.push(new MemoryDirectoryNode(0, 0, 0));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
class MemoryDirectoryNode {
|
|
81
|
+
constructor(mode, ino, now) {
|
|
82
|
+
this.mode = mode;
|
|
83
|
+
this.ino = ino;
|
|
84
|
+
this.atimeMs = now;
|
|
85
|
+
this.mtimeMs = now;
|
|
86
|
+
this.ctimeMs = now;
|
|
87
|
+
this.birthtimeMs = now;
|
|
88
|
+
this.entries = new Map();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
Object.assign(MemoryDirectoryNode.prototype, {
|
|
92
|
+
type: "directory",
|
|
93
|
+
revision: 0,
|
|
94
|
+
nlink: 1,
|
|
95
|
+
references: 0,
|
|
96
|
+
cachedNlink: 2,
|
|
97
|
+
cachedNlinkRev: 0,
|
|
98
|
+
});
|
|
50
99
|
class MemoryCache {
|
|
51
100
|
allocations = [];
|
|
52
101
|
files = [];
|
|
102
|
+
directories = [];
|
|
103
|
+
removedScratch = [];
|
|
53
104
|
lastFastDirPrefix = "";
|
|
54
105
|
lastFastDirNode;
|
|
55
106
|
lastFastFilePath = "";
|
|
107
|
+
lastFastFileName = "";
|
|
56
108
|
lastFastFileNode;
|
|
57
109
|
clearWrites() {
|
|
58
110
|
this.lastFastDirPrefix = "";
|
|
59
111
|
this.lastFastDirNode = undefined;
|
|
60
112
|
this.lastFastFilePath = "";
|
|
113
|
+
this.lastFastFileName = "";
|
|
61
114
|
this.lastFastFileNode = undefined;
|
|
62
115
|
}
|
|
63
116
|
}
|
|
@@ -70,6 +123,14 @@ const ext4HtreeEof64 = (1n << 63n) - 1n;
|
|
|
70
123
|
const extractionStreamGuard = Symbol("extractionStreamGuard");
|
|
71
124
|
const ownedStats = new WeakMap();
|
|
72
125
|
const ownedStores = new WeakMap();
|
|
126
|
+
// Ensure V8 uses Tagged representation for timestamp fields so all 4 fields share one HeapNumber pointer.
|
|
127
|
+
{
|
|
128
|
+
const dummyAlloc = new MemoryAllocation(new Uint8Array(0), new MemoryLedger(normalizeMemoryFileSystemLimits({})));
|
|
129
|
+
const dummyFile = new MemoryFileNode(0, 0, null, 0, dummyAlloc, dummyAlloc.data);
|
|
130
|
+
dummyFile.atimeMs = dummyFile.mtimeMs = dummyFile.ctimeMs = dummyFile.birthtimeMs = 1700000000000;
|
|
131
|
+
const dummyDir = new MemoryDirectoryNode(0, 0, null);
|
|
132
|
+
dummyDir.atimeMs = dummyDir.mtimeMs = dummyDir.ctimeMs = dummyDir.birthtimeMs = 1700000000000;
|
|
133
|
+
}
|
|
73
134
|
// Forwarded receivers share the ledger, so mutations invalidate the owner's cache.
|
|
74
135
|
const memoryCaches = new WeakMap();
|
|
75
136
|
const registeredAuthorities = new WeakSet();
|
|
@@ -127,6 +188,107 @@ export async function bindConditionalMutation(identityScope, binding, action) {
|
|
|
127
188
|
}
|
|
128
189
|
}
|
|
129
190
|
}
|
|
191
|
+
const STREAM_DONE_RESULT = Object.freeze({ done: true, value: undefined });
|
|
192
|
+
const STREAM_RESOLVED_DONE = Promise.resolve(STREAM_DONE_RESULT);
|
|
193
|
+
class MemoryReadStream {
|
|
194
|
+
constructor(fs, path, options) {
|
|
195
|
+
this.fs = fs;
|
|
196
|
+
this.path = path;
|
|
197
|
+
this.options = options;
|
|
198
|
+
this.abortSignal = options.signal;
|
|
199
|
+
this.initialized = false;
|
|
200
|
+
this.finished = false;
|
|
201
|
+
this.offset = 0;
|
|
202
|
+
this.end = 0;
|
|
203
|
+
this.chunkSize = preferredIoBlockSize;
|
|
204
|
+
this.node = undefined;
|
|
205
|
+
this.allocation = undefined;
|
|
206
|
+
this.data = undefined;
|
|
207
|
+
}
|
|
208
|
+
[Symbol.asyncIterator]() {
|
|
209
|
+
if (this.initialized || this.finished) {
|
|
210
|
+
return new MemoryReadStream(this.fs, this.path, this.options);
|
|
211
|
+
}
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
_release() {
|
|
215
|
+
if (this.finished)
|
|
216
|
+
return;
|
|
217
|
+
this.finished = true;
|
|
218
|
+
if (this.allocation) {
|
|
219
|
+
this.allocation.release();
|
|
220
|
+
this.allocation = undefined;
|
|
221
|
+
}
|
|
222
|
+
if (this.node) {
|
|
223
|
+
const releasedNode = this.node;
|
|
224
|
+
this.node = undefined;
|
|
225
|
+
this.fs.releaseReference(releasedNode, this.path);
|
|
226
|
+
}
|
|
227
|
+
this.data = undefined;
|
|
228
|
+
}
|
|
229
|
+
tryNextSync() {
|
|
230
|
+
if (this.finished)
|
|
231
|
+
return STREAM_DONE_RESULT;
|
|
232
|
+
try {
|
|
233
|
+
const options = this.options;
|
|
234
|
+
options.signal?.throwIfAborted();
|
|
235
|
+
if (!this.initialized) {
|
|
236
|
+
this.initialized = true;
|
|
237
|
+
const fs = this.fs;
|
|
238
|
+
const path = this.path;
|
|
239
|
+
const start = options.start ?? 0;
|
|
240
|
+
const chunkSize = options.chunkSize ?? preferredIoBlockSize;
|
|
241
|
+
this.chunkSize = chunkSize;
|
|
242
|
+
fs.integer(start, "readStream", path);
|
|
243
|
+
fs.integer(chunkSize, "readStream", path);
|
|
244
|
+
if (chunkSize === 0)
|
|
245
|
+
fs.fail("EINVAL", "readStream", path);
|
|
246
|
+
if (options.endExclusive !== undefined) {
|
|
247
|
+
fs.integer(options.endExclusive, "readStream", path);
|
|
248
|
+
if (options.endExclusive < start)
|
|
249
|
+
fs.fail("EINVAL", "readStream", path);
|
|
250
|
+
}
|
|
251
|
+
const target = fs.file(path, "readStream");
|
|
252
|
+
fs.permission(target, 4, "readStream", path);
|
|
253
|
+
fs.ledger.reserve(path.length * 2, 1, "readStream", path);
|
|
254
|
+
target.references++;
|
|
255
|
+
this.node = target;
|
|
256
|
+
this.allocation = target.allocation;
|
|
257
|
+
target.allocation.retain();
|
|
258
|
+
const data = target.data;
|
|
259
|
+
this.data = data;
|
|
260
|
+
this.offset = start;
|
|
261
|
+
this.end = Math.min(options.endExclusive ?? data.byteLength, data.byteLength);
|
|
262
|
+
target.atimeMs = Date.now();
|
|
263
|
+
}
|
|
264
|
+
if (this.offset < this.end) {
|
|
265
|
+
const nextEnd = Math.min(this.offset + this.chunkSize, this.end);
|
|
266
|
+
const chunk = this.data.slice(this.offset, nextEnd);
|
|
267
|
+
this.offset = nextEnd;
|
|
268
|
+
return { done: false, value: chunk };
|
|
269
|
+
}
|
|
270
|
+
this._release();
|
|
271
|
+
return STREAM_DONE_RESULT;
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
this._release();
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
next() {
|
|
279
|
+
try {
|
|
280
|
+
const res = this.tryNextSync();
|
|
281
|
+
return res.done ? STREAM_RESOLVED_DONE : Promise.resolve(res);
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
return Promise.reject(error);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return() {
|
|
288
|
+
this._release();
|
|
289
|
+
return STREAM_RESOLVED_DONE;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
130
292
|
export class MemoryFileSystem {
|
|
131
293
|
capabilitiesFor;
|
|
132
294
|
capabilities = ((filesystem) => {
|
|
@@ -142,7 +304,7 @@ export class MemoryFileSystem {
|
|
|
142
304
|
permissions: true,
|
|
143
305
|
timestamps: true,
|
|
144
306
|
atomicRename: true,
|
|
145
|
-
atomicFileStaging: true, retainedStagingCleanup: true, atomicStagingAncestry: true, atomicFileMutation: true, atomicEntryRemoval: true, atomicEntryRemovalReceipt: true, atomicTreeRemoval: true,
|
|
307
|
+
atomicFileStaging: true, retainedStagingCleanup: true, retainedStagingWrite: true, atomicStagingAncestry: true, atomicFileMutation: true, atomicEntryRemoval: true, atomicEntryRemovalReceipt: true, atomicTreeRemoval: true,
|
|
146
308
|
synchronousDirectoryValidation: true, synchronousStagingResolution: true, guardedStagingPublication: true,
|
|
147
309
|
atomicDirectoryMetadata: true,
|
|
148
310
|
streamingRead: true,
|
|
@@ -160,6 +322,9 @@ export class MemoryFileSystem {
|
|
|
160
322
|
symlinkCount = 0;
|
|
161
323
|
constructor(options = {}) {
|
|
162
324
|
this.ledger = new MemoryLedger(normalizeMemoryFileSystemLimits(options));
|
|
325
|
+
if (this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity) {
|
|
326
|
+
replenishSharedMemoryPools();
|
|
327
|
+
}
|
|
163
328
|
memoryCaches.set(this.ledger, new MemoryCache());
|
|
164
329
|
this.ledger.reserve(0, 1, "mkdir", "/");
|
|
165
330
|
this.root = this.directory(0o755);
|
|
@@ -250,8 +415,8 @@ export class MemoryFileSystem {
|
|
|
250
415
|
}
|
|
251
416
|
retained.set("/", this.root);
|
|
252
417
|
}
|
|
253
|
-
const capabilities =
|
|
254
|
-
const allowed = new Set(["mkdir", "rm", "rmdir", "rename", "symlink", "link", "chmod", "utimes", "writeFile", "appendFile", "writeStream", "writeFileConditional", "removeFileConditional"]);
|
|
418
|
+
const capabilities = this.capabilities;
|
|
419
|
+
const allowed = new Set(["mkdir", "rm", "rmdir", "rename", "symlink", "link", "chmod", "utimes", "writeFile", "appendFile", "writeStream", "writeFileConditional", "removeFileConditional", "removeEntryConditional", "prepareDirectory", "createStagedFile", "publishStagedFile", "removeStagedFile"]);
|
|
255
420
|
const reads = new Set(["access", "capabilitiesFor", "compareEntry", "lstat", "stat", "readFile", "readStream", "readdir", "readlink", "realpath"]);
|
|
256
421
|
const check = (path, followFinal) => {
|
|
257
422
|
if (!roots.some(root => root === "/" || path === root || path.startsWith(`${root}/`)))
|
|
@@ -287,7 +452,7 @@ export class MemoryFileSystem {
|
|
|
287
452
|
throw new FsError("ENOTSUP", { syscall: String(property) });
|
|
288
453
|
// Stock mutations resolve and commit synchronously. Streamed writes
|
|
289
454
|
// repeat the boundary and file-binding check at each backend mutation.
|
|
290
|
-
const paths = property === "symlink" ? [args[1]] : property === "link" || property === "rename" ? args.slice(0, 2) : [args[0]];
|
|
455
|
+
const paths = property === "publishStagedFile" ? [args[1], args[0].directory.path] : property === "removeStagedFile" ? [args[0].directory.path] : property === "symlink" ? [args[1]] : property === "link" || property === "rename" ? args.slice(0, 2) : [args[0]];
|
|
291
456
|
for (const path of paths) {
|
|
292
457
|
if (typeof path !== "string")
|
|
293
458
|
throw new FsError("EINVAL");
|
|
@@ -301,6 +466,24 @@ export class MemoryFileSystem {
|
|
|
301
466
|
this.fail("EPERM", "writeStream", path);
|
|
302
467
|
} };
|
|
303
468
|
}
|
|
469
|
+
if (property === "createStagedFile")
|
|
470
|
+
return Reflect.apply(value, target, args).then(staging => {
|
|
471
|
+
const writer = staging.writer;
|
|
472
|
+
if (!writer)
|
|
473
|
+
return staging;
|
|
474
|
+
return Object.freeze({ ...staging, writer: Object.freeze({
|
|
475
|
+
write: async (bytes, controls = {}) => {
|
|
476
|
+
controls.signal?.throwIfAborted();
|
|
477
|
+
check(staging.file.path, true);
|
|
478
|
+
await writer.write(bytes, controls);
|
|
479
|
+
},
|
|
480
|
+
finish: async (controls = {}) => {
|
|
481
|
+
controls.signal?.throwIfAborted();
|
|
482
|
+
check(staging.file.path, true);
|
|
483
|
+
return writer.finish(controls);
|
|
484
|
+
},
|
|
485
|
+
}) });
|
|
486
|
+
});
|
|
304
487
|
return Reflect.apply(value, target, args);
|
|
305
488
|
};
|
|
306
489
|
},
|
|
@@ -314,7 +497,39 @@ export class MemoryFileSystem {
|
|
|
314
497
|
};
|
|
315
498
|
}
|
|
316
499
|
directory(mode) {
|
|
317
|
-
|
|
500
|
+
const now = Date.now();
|
|
501
|
+
const fullMode = typeModes.directory | mode;
|
|
502
|
+
const ino = this.nextInode++;
|
|
503
|
+
const pooled = sharedDirectoryNodePool.pop() ?? memoryCaches.get(this.ledger)?.directories.pop();
|
|
504
|
+
if (pooled) {
|
|
505
|
+
pooled.mode = fullMode;
|
|
506
|
+
pooled.ino = ino;
|
|
507
|
+
pooled.nlink = 1;
|
|
508
|
+
pooled.references = 0;
|
|
509
|
+
pooled.revision = 0;
|
|
510
|
+
pooled.atimeMs = now;
|
|
511
|
+
pooled.mtimeMs = now;
|
|
512
|
+
pooled.ctimeMs = now;
|
|
513
|
+
pooled.birthtimeMs = now;
|
|
514
|
+
pooled.cachedNlink = 2;
|
|
515
|
+
pooled.cachedNlinkRev = 0;
|
|
516
|
+
return pooled;
|
|
517
|
+
}
|
|
518
|
+
return new MemoryDirectoryNode(fullMode, ino, now);
|
|
519
|
+
}
|
|
520
|
+
addDirectoryNode(parent, name, mode, syscall, path) {
|
|
521
|
+
const bytes = name.length * 2;
|
|
522
|
+
this.ledger.reserve(bytes, 2, syscall, path);
|
|
523
|
+
try {
|
|
524
|
+
const node = this.directory(mode);
|
|
525
|
+
parent.entries.set(name, node);
|
|
526
|
+
this.changed(parent);
|
|
527
|
+
return node;
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
this.ledger.release(bytes, 2);
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
318
533
|
}
|
|
319
534
|
addNode(parent, name, create, syscall, path, retainedBytes = 0) {
|
|
320
535
|
const bytes = name.length * 2 + retainedBytes;
|
|
@@ -344,25 +559,46 @@ export class MemoryFileSystem {
|
|
|
344
559
|
if (node.type === "file") {
|
|
345
560
|
if (cache.lastFastFileNode === node) {
|
|
346
561
|
cache.lastFastFilePath = "";
|
|
562
|
+
cache.lastFastFileName = "";
|
|
347
563
|
cache.lastFastFileNode = undefined;
|
|
348
564
|
}
|
|
349
565
|
this.totalBytes -= node.byteLength;
|
|
350
566
|
const alloc = node.allocation;
|
|
351
567
|
node.view = undefined;
|
|
568
|
+
node.allocation = DUMMY_POOL_ALLOCATION;
|
|
352
569
|
alloc.release();
|
|
353
|
-
if (alloc.isReleased64()
|
|
354
|
-
|
|
570
|
+
if (alloc.isReleased64()) {
|
|
571
|
+
alloc.detachLedger(DUMMY_POOL_LEDGER);
|
|
572
|
+
if (this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity && sharedAllocationPool.length < 128) {
|
|
573
|
+
sharedAllocationPool.push(alloc);
|
|
574
|
+
}
|
|
575
|
+
else if (cache.allocations.length < 128) {
|
|
576
|
+
cache.allocations.push(alloc);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (sharedFileNodePool.length < 128) {
|
|
580
|
+
sharedFileNodePool.push(node);
|
|
355
581
|
}
|
|
356
|
-
if (cache.files.length < 128) {
|
|
582
|
+
else if (cache.files.length < 128) {
|
|
357
583
|
cache.files.push(node);
|
|
358
584
|
}
|
|
359
585
|
}
|
|
360
586
|
else if (node.type === "symlink") {
|
|
361
587
|
this.symlinkCount--;
|
|
362
588
|
}
|
|
363
|
-
else
|
|
364
|
-
cache.
|
|
365
|
-
|
|
589
|
+
else {
|
|
590
|
+
if (cache.lastFastDirNode === node) {
|
|
591
|
+
cache.lastFastDirPrefix = "";
|
|
592
|
+
cache.lastFastDirNode = undefined;
|
|
593
|
+
}
|
|
594
|
+
if (node.entries.size === 0) {
|
|
595
|
+
if (sharedDirectoryNodePool.length < 32) {
|
|
596
|
+
sharedDirectoryNodePool.push(node);
|
|
597
|
+
}
|
|
598
|
+
else if (cache.directories.length < 32) {
|
|
599
|
+
cache.directories.push(node);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
366
602
|
}
|
|
367
603
|
}
|
|
368
604
|
releaseReference(node, path) {
|
|
@@ -408,9 +644,9 @@ export class MemoryFileSystem {
|
|
|
408
644
|
if (!Number.isSafeInteger(value) || value < 0)
|
|
409
645
|
this.fail("EINVAL", syscall, path);
|
|
410
646
|
}
|
|
411
|
-
changed(node) {
|
|
647
|
+
changed(node, now = Date.now()) {
|
|
412
648
|
node.revision = node.revision < 1073741823 ? (node.revision + 1) | 0 : Math.min(Number.MAX_SAFE_INTEGER + 1, node.revision + 1);
|
|
413
|
-
node.mtimeMs = node.ctimeMs =
|
|
649
|
+
node.mtimeMs = node.ctimeMs = now;
|
|
414
650
|
}
|
|
415
651
|
resolveNode(path, syscall, followFinal = true) {
|
|
416
652
|
const cache = memoryCaches.get(this.ledger);
|
|
@@ -462,7 +698,7 @@ export class MemoryFileSystem {
|
|
|
462
698
|
}
|
|
463
699
|
resolve(path, syscall, options = emptyResolveOptions) {
|
|
464
700
|
this.validatePath(path, syscall);
|
|
465
|
-
if (this.symlinkCount === 0 && options.
|
|
701
|
+
if (this.symlinkCount === 0 && options.resizeCreate === undefined
|
|
466
702
|
&& options.resolutionSteps === undefined && isCleanAbsolutePath(path)) {
|
|
467
703
|
let current = this.root;
|
|
468
704
|
let start = 1;
|
|
@@ -473,7 +709,11 @@ export class MemoryFileSystem {
|
|
|
473
709
|
const name = path.slice(start);
|
|
474
710
|
if (exceedsComponentByteLimit(name))
|
|
475
711
|
this.fail("ENAMETOOLONG", syscall, path);
|
|
476
|
-
|
|
712
|
+
let node = current.entries.get(name);
|
|
713
|
+
if (!node && options.createDirectories !== undefined) {
|
|
714
|
+
this.permission(current, 3, syscall, path);
|
|
715
|
+
node = this.addDirectoryNode(current, name, options.createDirectories, syscall, path);
|
|
716
|
+
}
|
|
477
717
|
if (!node && !options.allowMissing)
|
|
478
718
|
this.fail("ENOENT", syscall, path);
|
|
479
719
|
return { node, parent: current, name, path };
|
|
@@ -481,7 +721,11 @@ export class MemoryFileSystem {
|
|
|
481
721
|
const name = path.slice(start, slash);
|
|
482
722
|
if (exceedsComponentByteLimit(name))
|
|
483
723
|
this.fail("ENAMETOOLONG", syscall, path);
|
|
484
|
-
|
|
724
|
+
let next = current.entries.get(name);
|
|
725
|
+
if (!next && options.createDirectories !== undefined) {
|
|
726
|
+
this.permission(current, 3, syscall, path);
|
|
727
|
+
next = this.addDirectoryNode(current, name, options.createDirectories, syscall, path);
|
|
728
|
+
}
|
|
485
729
|
if (!next)
|
|
486
730
|
this.fail("ENOENT", syscall, path);
|
|
487
731
|
if (next.type !== "directory")
|
|
@@ -645,6 +889,12 @@ export class MemoryFileSystem {
|
|
|
645
889
|
return location;
|
|
646
890
|
}
|
|
647
891
|
terminalDot(path) {
|
|
892
|
+
const len = path.length;
|
|
893
|
+
if (len === 0)
|
|
894
|
+
return false;
|
|
895
|
+
const last = path.charCodeAt(len - 1);
|
|
896
|
+
if (last !== 46 && last !== 47)
|
|
897
|
+
return false;
|
|
648
898
|
return /(?:^|\/)\.{1,2}\/*$/.test(path);
|
|
649
899
|
}
|
|
650
900
|
directoryNlink(node) {
|
|
@@ -716,13 +966,30 @@ export class MemoryFileSystem {
|
|
|
716
966
|
allocate(length, syscall, path) {
|
|
717
967
|
this.ledger.fileSize(length, syscall, path);
|
|
718
968
|
this.ledger.reserve(length, 0, syscall, path);
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
969
|
+
if (length === 0) {
|
|
970
|
+
return new MemoryAllocation(EMPTY_ALLOC_BYTES, this.ledger);
|
|
971
|
+
}
|
|
972
|
+
if (length === 64) {
|
|
973
|
+
const pooled = (this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity
|
|
974
|
+
? sharedAllocationPool.pop()
|
|
975
|
+
: undefined) ?? memoryCaches.get(this.ledger).allocations.pop();
|
|
976
|
+
if (pooled) {
|
|
977
|
+
pooled.reuse(this.ledger);
|
|
978
|
+
return pooled;
|
|
979
|
+
}
|
|
724
980
|
}
|
|
725
981
|
try {
|
|
982
|
+
// A shared slab's backing buffer exceeds the admitted slice capacity.
|
|
983
|
+
// Finite file/storage ceilings require independently owned exact buffers.
|
|
984
|
+
if (length <= 64 && this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity) {
|
|
985
|
+
if (smallAllocOffset + length > SMALL_ALLOC_SLAB_SIZE) {
|
|
986
|
+
smallAllocSlab = new Uint8Array(SMALL_ALLOC_SLAB_SIZE);
|
|
987
|
+
smallAllocOffset = 0;
|
|
988
|
+
}
|
|
989
|
+
const slice = smallAllocSlab.subarray(smallAllocOffset, smallAllocOffset + length);
|
|
990
|
+
smallAllocOffset += length;
|
|
991
|
+
return new MemoryAllocation(slice, this.ledger);
|
|
992
|
+
}
|
|
726
993
|
return new MemoryAllocation(new Uint8Array(length), this.ledger);
|
|
727
994
|
}
|
|
728
995
|
catch (cause) {
|
|
@@ -825,7 +1092,7 @@ export class MemoryFileSystem {
|
|
|
825
1092
|
const now = Date.now();
|
|
826
1093
|
const fileMode = typeModes.file | target.mode;
|
|
827
1094
|
const view = capacity === length ? allocation.data : undefined;
|
|
828
|
-
const pooled = memoryCaches.get(this.ledger).files.pop();
|
|
1095
|
+
const pooled = sharedFileNodePool.pop() ?? memoryCaches.get(this.ledger).files.pop();
|
|
829
1096
|
if (pooled) {
|
|
830
1097
|
pooled.mode = fileMode;
|
|
831
1098
|
pooled.ino = this.nextInode++;
|
|
@@ -873,7 +1140,13 @@ export class MemoryFileSystem {
|
|
|
873
1140
|
let allocation = node.allocation;
|
|
874
1141
|
if (length > allocation.data.byteLength) {
|
|
875
1142
|
this.ledger.check(length, 0, syscall, path);
|
|
876
|
-
const
|
|
1143
|
+
const baseCap = curLen === 0 && length >= 8192 && length <= 16384 &&
|
|
1144
|
+
this.ledger.limits.maxFileBytes >= 65536 &&
|
|
1145
|
+
this.ledger.availableBytes >= 65536 &&
|
|
1146
|
+
(this.ledger.limits.maxBytes === undefined || this.ledger.limits.maxBytes - this.totalBytes >= 65536)
|
|
1147
|
+
? 65536
|
|
1148
|
+
: Math.max(length, curLen * 2, 64);
|
|
1149
|
+
const capacity = Math.min(baseCap, this.ledger.limits.maxFileBytes, this.ledger.availableBytes);
|
|
877
1150
|
allocation = this.allocate(capacity, syscall, path);
|
|
878
1151
|
}
|
|
879
1152
|
try {
|
|
@@ -977,13 +1250,12 @@ export class MemoryFileSystem {
|
|
|
977
1250
|
if (current && append) {
|
|
978
1251
|
this.writeAt(current, data, current.byteLength, syscall, path);
|
|
979
1252
|
cache.lastFastFilePath = path;
|
|
1253
|
+
cache.lastFastFileName = name;
|
|
980
1254
|
cache.lastFastFileNode = current;
|
|
981
1255
|
return;
|
|
982
1256
|
}
|
|
983
1257
|
if (!current) {
|
|
984
|
-
const capacity = length > 0 &&
|
|
985
|
-
? Math.min(Math.max(length, 64), this.ledger.limits.maxFileBytes, this.ledger.availableBytes - nameBytes)
|
|
986
|
-
: length;
|
|
1258
|
+
const capacity = length > 0 && length < 64 && this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity ? 64 : length;
|
|
987
1259
|
const allocation = this.allocate(capacity, syscall, path);
|
|
988
1260
|
try {
|
|
989
1261
|
allocation.data.set(data);
|
|
@@ -992,7 +1264,7 @@ export class MemoryFileSystem {
|
|
|
992
1264
|
const now = Date.now();
|
|
993
1265
|
const fileMode = typeModes.file | mode;
|
|
994
1266
|
const view = capacity === length ? allocation.data : undefined;
|
|
995
|
-
let node = cache.files.pop();
|
|
1267
|
+
let node = sharedFileNodePool.pop() ?? cache.files.pop();
|
|
996
1268
|
if (node) {
|
|
997
1269
|
node.mode = fileMode;
|
|
998
1270
|
node.ino = this.nextInode++;
|
|
@@ -1010,16 +1282,16 @@ export class MemoryFileSystem {
|
|
|
1010
1282
|
else {
|
|
1011
1283
|
node = new MemoryFileNode(fileMode, this.nextInode++, now, length, allocation, view);
|
|
1012
1284
|
}
|
|
1013
|
-
this.admitSize(undefined, length, syscall, path);
|
|
1014
1285
|
const prevNlink = parent.cachedNlinkRev === parent.revision ? parent.cachedNlink : (parent.entries.size === 0 ? 2 : undefined);
|
|
1015
1286
|
parent.entries.set(name, node);
|
|
1016
1287
|
this.totalBytes += length;
|
|
1017
|
-
this.changed(parent);
|
|
1288
|
+
this.changed(parent, now);
|
|
1018
1289
|
if (prevNlink !== undefined) {
|
|
1019
1290
|
parent.cachedNlink = prevNlink;
|
|
1020
1291
|
parent.cachedNlinkRev = parent.revision;
|
|
1021
1292
|
}
|
|
1022
1293
|
cache.lastFastFilePath = path;
|
|
1294
|
+
cache.lastFastFileName = name;
|
|
1023
1295
|
cache.lastFastFileNode = node;
|
|
1024
1296
|
}
|
|
1025
1297
|
catch (error) {
|
|
@@ -1037,12 +1309,169 @@ export class MemoryFileSystem {
|
|
|
1037
1309
|
try {
|
|
1038
1310
|
this.replaceData(current, copied);
|
|
1039
1311
|
this.changed(current);
|
|
1312
|
+
cache.lastFastFilePath = path;
|
|
1313
|
+
cache.lastFastFileName = name;
|
|
1314
|
+
cache.lastFastFileNode = current;
|
|
1040
1315
|
}
|
|
1041
1316
|
catch (error) {
|
|
1042
1317
|
copied.release();
|
|
1043
1318
|
throw error;
|
|
1044
1319
|
}
|
|
1045
1320
|
}
|
|
1321
|
+
writeMemoryFileInDirFast(dirPrefix, name, data, append, mode) {
|
|
1322
|
+
const cache = memoryCaches.get(this.ledger);
|
|
1323
|
+
const syscall = append ? "appendFile" : "writeFile";
|
|
1324
|
+
try {
|
|
1325
|
+
if (append &&
|
|
1326
|
+
dirPrefix === cache.lastFastDirPrefix &&
|
|
1327
|
+
name === cache.lastFastFileName &&
|
|
1328
|
+
cache.lastFastFileNode !== undefined &&
|
|
1329
|
+
cache.lastFastFileNode.nlink !== 0 &&
|
|
1330
|
+
cache.lastFastDirNode !== undefined &&
|
|
1331
|
+
cache.lastFastDirNode.nlink !== 0 &&
|
|
1332
|
+
(cache.lastFastDirNode.mode & 1) !== 0 &&
|
|
1333
|
+
(cache.lastFastFileNode.mode & 2) !== 0) {
|
|
1334
|
+
const current = cache.lastFastFileNode;
|
|
1335
|
+
const length = current.byteLength + data.byteLength;
|
|
1336
|
+
this.admitSize(current, length, syscall, name);
|
|
1337
|
+
const growth = length > current.allocation.data.byteLength ? length : 0;
|
|
1338
|
+
this.ledger.check(data.byteLength + growth, 0, syscall, name);
|
|
1339
|
+
this.writeAt(current, data, current.byteLength, syscall, name);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
let parent = this.root;
|
|
1343
|
+
const cachedDir = cache.lastFastDirNode;
|
|
1344
|
+
if (cachedDir !== undefined &&
|
|
1345
|
+
cachedDir.nlink !== 0 &&
|
|
1346
|
+
(cachedDir.mode & 1) !== 0 &&
|
|
1347
|
+
dirPrefix === cache.lastFastDirPrefix) {
|
|
1348
|
+
parent = cachedDir;
|
|
1349
|
+
if (exceedsComponentByteLimit(name))
|
|
1350
|
+
this.fail("ENAMETOOLONG", syscall, name);
|
|
1351
|
+
}
|
|
1352
|
+
else {
|
|
1353
|
+
let start = 1;
|
|
1354
|
+
while (start < dirPrefix.length) {
|
|
1355
|
+
this.permission(parent, 1, syscall, name);
|
|
1356
|
+
const slash = dirPrefix.indexOf("/", start);
|
|
1357
|
+
const seg = slash === -1 ? dirPrefix.slice(start) : dirPrefix.slice(start, slash);
|
|
1358
|
+
if (exceedsComponentByteLimit(seg))
|
|
1359
|
+
this.fail("ENAMETOOLONG", syscall, name);
|
|
1360
|
+
const next = parent.entries.get(seg);
|
|
1361
|
+
if (!next)
|
|
1362
|
+
this.fail("ENOENT", syscall, name);
|
|
1363
|
+
if (next.type !== "directory")
|
|
1364
|
+
this.fail("ENOTDIR", syscall, name);
|
|
1365
|
+
parent = next;
|
|
1366
|
+
if (slash === -1)
|
|
1367
|
+
break;
|
|
1368
|
+
start = slash + 1;
|
|
1369
|
+
}
|
|
1370
|
+
this.permission(parent, 1, syscall, name);
|
|
1371
|
+
if (exceedsComponentByteLimit(name))
|
|
1372
|
+
this.fail("ENAMETOOLONG", syscall, name);
|
|
1373
|
+
if (dirPrefix.length > 1 && (parent.mode & 1) !== 0) {
|
|
1374
|
+
cache.lastFastDirPrefix = dirPrefix;
|
|
1375
|
+
cache.lastFastDirNode = parent;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
const existing = parent.entries.get(name);
|
|
1379
|
+
if (existing) {
|
|
1380
|
+
if (existing.type !== "file")
|
|
1381
|
+
this.fail("EISDIR", syscall, name);
|
|
1382
|
+
this.permission(existing, 2, syscall, name);
|
|
1383
|
+
}
|
|
1384
|
+
else {
|
|
1385
|
+
this.permission(parent, 3, syscall, name);
|
|
1386
|
+
}
|
|
1387
|
+
const current = existing;
|
|
1388
|
+
const length = (append ? current?.byteLength ?? 0 : 0) + data.byteLength;
|
|
1389
|
+
this.admitSize(current, length, syscall, name);
|
|
1390
|
+
const growth = append && length > (current?.allocation.data.byteLength ?? 0) ? length : 0;
|
|
1391
|
+
const nameBytes = name.length * 2;
|
|
1392
|
+
this.ledger.check(data.byteLength + growth + (current ? 0 : nameBytes), current ? 0 : 2, syscall, name);
|
|
1393
|
+
if (current && append) {
|
|
1394
|
+
this.writeAt(current, data, current.byteLength, syscall, name);
|
|
1395
|
+
cache.lastFastFilePath = "";
|
|
1396
|
+
cache.lastFastFileName = name;
|
|
1397
|
+
cache.lastFastFileNode = current;
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
if (!current) {
|
|
1401
|
+
const capacity = length > 0 && length < 64 && this.ledger.hasInfiniteRetained && this.ledger.limits.maxFileBytes === Infinity ? 64 : length;
|
|
1402
|
+
const allocation = this.allocate(capacity, syscall, name);
|
|
1403
|
+
try {
|
|
1404
|
+
allocation.data.set(data);
|
|
1405
|
+
this.ledger.reserve(nameBytes, 2, syscall, name);
|
|
1406
|
+
try {
|
|
1407
|
+
const now = Date.now();
|
|
1408
|
+
const fileMode = typeModes.file | mode;
|
|
1409
|
+
const view = capacity === length ? allocation.data : undefined;
|
|
1410
|
+
let node = sharedFileNodePool.pop() ?? cache.files.pop();
|
|
1411
|
+
if (node) {
|
|
1412
|
+
node.mode = fileMode;
|
|
1413
|
+
node.ino = this.nextInode++;
|
|
1414
|
+
node.nlink = 1;
|
|
1415
|
+
node.references = 0;
|
|
1416
|
+
node.revision = 0;
|
|
1417
|
+
node.atimeMs = now;
|
|
1418
|
+
node.mtimeMs = now;
|
|
1419
|
+
node.ctimeMs = now;
|
|
1420
|
+
node.birthtimeMs = now;
|
|
1421
|
+
node.byteLength = length;
|
|
1422
|
+
node.view = view;
|
|
1423
|
+
node.allocation = allocation;
|
|
1424
|
+
}
|
|
1425
|
+
else {
|
|
1426
|
+
node = new MemoryFileNode(fileMode, this.nextInode++, now, length, allocation, view);
|
|
1427
|
+
}
|
|
1428
|
+
const prevNlink = parent.cachedNlinkRev === parent.revision ? parent.cachedNlink : (parent.entries.size === 0 ? 2 : undefined);
|
|
1429
|
+
parent.entries.set(name, node);
|
|
1430
|
+
this.totalBytes += length;
|
|
1431
|
+
this.changed(parent, now);
|
|
1432
|
+
if (prevNlink !== undefined) {
|
|
1433
|
+
parent.cachedNlink = prevNlink;
|
|
1434
|
+
parent.cachedNlinkRev = parent.revision;
|
|
1435
|
+
}
|
|
1436
|
+
cache.lastFastFilePath = "";
|
|
1437
|
+
cache.lastFastFileName = name;
|
|
1438
|
+
cache.lastFastFileNode = node;
|
|
1439
|
+
}
|
|
1440
|
+
catch (error) {
|
|
1441
|
+
this.ledger.release(nameBytes, 2);
|
|
1442
|
+
throw error;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
catch (error) {
|
|
1446
|
+
allocation.release();
|
|
1447
|
+
throw error;
|
|
1448
|
+
}
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
const copied = this.bytes(data, syscall, name);
|
|
1452
|
+
try {
|
|
1453
|
+
this.replaceData(current, copied);
|
|
1454
|
+
this.changed(current);
|
|
1455
|
+
cache.lastFastFilePath = "";
|
|
1456
|
+
cache.lastFastFileName = name;
|
|
1457
|
+
cache.lastFastFileNode = current;
|
|
1458
|
+
}
|
|
1459
|
+
catch (error) {
|
|
1460
|
+
copied.release();
|
|
1461
|
+
throw error;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
catch (error) {
|
|
1465
|
+
if (error instanceof FsError && error.path === name) {
|
|
1466
|
+
throw new FsError(error.code, {
|
|
1467
|
+
syscall: error.syscall ?? syscall,
|
|
1468
|
+
path: dirPrefix + name,
|
|
1469
|
+
...(error.dest === undefined ? {} : { dest: error.dest }),
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
throw error;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1046
1475
|
async readFile(path, options = {}) {
|
|
1047
1476
|
options.signal?.throwIfAborted();
|
|
1048
1477
|
if (options.maxBytes !== undefined)
|
|
@@ -1143,6 +1572,8 @@ export class MemoryFileSystem {
|
|
|
1143
1572
|
location.parent.entries.set(location.name, directory);
|
|
1144
1573
|
if (file.type === "file")
|
|
1145
1574
|
this.totalBytes += file.data.byteLength;
|
|
1575
|
+
else
|
|
1576
|
+
this.symlinkCount++;
|
|
1146
1577
|
}
|
|
1147
1578
|
catch (error) {
|
|
1148
1579
|
allocation?.release();
|
|
@@ -1158,13 +1589,16 @@ export class MemoryFileSystem {
|
|
|
1158
1589
|
location.parent.references++;
|
|
1159
1590
|
directory.references++;
|
|
1160
1591
|
file.references++;
|
|
1161
|
-
return Object.freeze({ ...staging,
|
|
1592
|
+
return Object.freeze({ ...staging, ...this.retainStagingCleanup(staging, location.parent, directory, file, location.name, name) });
|
|
1162
1593
|
}
|
|
1163
1594
|
retainStagingCleanup(staging, parent, directory, file, directoryName, fileName) {
|
|
1164
1595
|
// Clear this state on release, so keeping the closed handle does not keep
|
|
1165
1596
|
// detached nodes alive outside the Memory ledger.
|
|
1166
1597
|
let retained = { parent, directory, file };
|
|
1167
|
-
|
|
1598
|
+
let expected = staging.file.stat;
|
|
1599
|
+
let writing = true;
|
|
1600
|
+
const receipt = () => ({ ...staging, file: { ...staging.file, stat: expected } });
|
|
1601
|
+
const ownedCleanup = createStagingCleanup(staging.directory.path, options => {
|
|
1168
1602
|
options.signal?.throwIfAborted();
|
|
1169
1603
|
const state = retained;
|
|
1170
1604
|
if (state.parent.nlink === 0 || state.parent.entries.get(directoryName) !== state.directory)
|
|
@@ -1176,7 +1610,7 @@ export class MemoryFileSystem {
|
|
|
1176
1610
|
const child = state.directory.entries.get(fileName);
|
|
1177
1611
|
if (child && child !== state.file)
|
|
1178
1612
|
this.fail("EAGAIN", "removeStagedFile", staging.file.path);
|
|
1179
|
-
this.removeStagingLocations(
|
|
1613
|
+
this.removeStagingLocations(receipt(), {
|
|
1180
1614
|
node: state.directory, parent: state.parent, name: directoryName, path: staging.directory.path,
|
|
1181
1615
|
}, { node: child, parent: state.directory, name: fileName, path: staging.file.path });
|
|
1182
1616
|
}, () => {
|
|
@@ -1186,6 +1620,43 @@ export class MemoryFileSystem {
|
|
|
1186
1620
|
this.releaseReference(state.directory, staging.directory.path);
|
|
1187
1621
|
this.releaseReference(state.parent, staging.parent.path);
|
|
1188
1622
|
});
|
|
1623
|
+
const cleanup = Object.freeze({
|
|
1624
|
+
remove: (options) => { writing = false; return ownedCleanup.remove(options); },
|
|
1625
|
+
close: () => { writing = false; return ownedCleanup.close(); },
|
|
1626
|
+
});
|
|
1627
|
+
if (file.type !== "file")
|
|
1628
|
+
return { cleanup };
|
|
1629
|
+
const inspect = (options) => {
|
|
1630
|
+
options.signal?.throwIfAborted();
|
|
1631
|
+
if (!retained || !writing)
|
|
1632
|
+
this.fail("EBADF", "stagedWrite", staging.file.path);
|
|
1633
|
+
const location = this.stagingLocations(receipt());
|
|
1634
|
+
if (location.file.node !== retained.file)
|
|
1635
|
+
this.fail("EAGAIN", "stagedWrite", staging.file.path);
|
|
1636
|
+
this.expectEntry(retained.file, expected, staging.file.path);
|
|
1637
|
+
this.permission(retained.parent, 3, "stagedWrite", staging.parent.path);
|
|
1638
|
+
this.permission(retained.directory, 3, "stagedWrite", staging.directory.path);
|
|
1639
|
+
return retained.file;
|
|
1640
|
+
};
|
|
1641
|
+
return { cleanup, writer: Object.freeze({
|
|
1642
|
+
write: async (bytes, options = {}) => {
|
|
1643
|
+
const node = inspect(options);
|
|
1644
|
+
if (!(bytes instanceof Uint8Array))
|
|
1645
|
+
throw new TypeError("Staged writes require Uint8Array data");
|
|
1646
|
+
this.writeAt(node, bytes, node.byteLength, "stagedWrite", staging.file.path);
|
|
1647
|
+
expected = Object.freeze(this.snapshot(node));
|
|
1648
|
+
},
|
|
1649
|
+
finish: async (options = {}) => {
|
|
1650
|
+
const node = inspect(options);
|
|
1651
|
+
this.changed(node);
|
|
1652
|
+
// changed updates modification time; archive creation metadata is final.
|
|
1653
|
+
node.atimeMs = staging.file.stat.atimeMs;
|
|
1654
|
+
node.mtimeMs = staging.file.stat.mtimeMs;
|
|
1655
|
+
writing = false;
|
|
1656
|
+
expected = Object.freeze(this.snapshot(node));
|
|
1657
|
+
return expected;
|
|
1658
|
+
},
|
|
1659
|
+
}) };
|
|
1189
1660
|
}
|
|
1190
1661
|
async prepareDirectoryAncestry(ancestors, options = {}) {
|
|
1191
1662
|
const entries = snapshotDirectoryAncestry(ancestors);
|
|
@@ -1529,31 +2000,39 @@ export class MemoryFileSystem {
|
|
|
1529
2000
|
this.permission(location.parent, 3, syscall, path);
|
|
1530
2001
|
if (node.type === "directory" && !recursive)
|
|
1531
2002
|
this.fail("EISDIR", syscall, path);
|
|
1532
|
-
memoryCaches.get(this.ledger)
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2003
|
+
const cache = memoryCaches.get(this.ledger);
|
|
2004
|
+
cache.clearWrites();
|
|
2005
|
+
const removed = sharedRemovedScratch;
|
|
2006
|
+
removed.length = 0;
|
|
2007
|
+
removed.push(node);
|
|
2008
|
+
sharedRemovedKeyChars = 0;
|
|
2009
|
+
sharedRemovedKeyEntries = 0;
|
|
2010
|
+
try {
|
|
2011
|
+
for (let index = 0; index < removed.length; index++) {
|
|
2012
|
+
const entry = removed[index];
|
|
2013
|
+
if (entry.type === "directory" && entry.entries.size > 0) {
|
|
2014
|
+
this.permission(entry, 7, syscall, path);
|
|
2015
|
+
entry.entries.forEach(collectRemovedChild);
|
|
2016
|
+
}
|
|
1545
2017
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
entry.
|
|
2018
|
+
location.parent.entries.delete(location.name);
|
|
2019
|
+
this.ledger.release((location.name.length + sharedRemovedKeyChars) * 2, 1 + sharedRemovedKeyEntries);
|
|
2020
|
+
const now = Date.now();
|
|
2021
|
+
for (let i = 0; i < removed.length; i++) {
|
|
2022
|
+
const entry = removed[i];
|
|
2023
|
+
if (entry.type === "directory") {
|
|
2024
|
+
entry.entries.clear();
|
|
2025
|
+
}
|
|
2026
|
+
entry.nlink--;
|
|
2027
|
+
if (entry.nlink !== 0 || entry.references !== 0) {
|
|
2028
|
+
entry.ctimeMs = now;
|
|
2029
|
+
entry.revision = Math.min(Number.MAX_SAFE_INTEGER + 1, entry.revision + 1);
|
|
2030
|
+
}
|
|
2031
|
+
this.releaseNode(entry);
|
|
1552
2032
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
this.releaseNode(entry);
|
|
2033
|
+
}
|
|
2034
|
+
finally {
|
|
2035
|
+
removed.length = 0;
|
|
1557
2036
|
}
|
|
1558
2037
|
this.changed(location.parent);
|
|
1559
2038
|
}
|
|
@@ -1882,94 +2361,7 @@ export class MemoryFileSystem {
|
|
|
1882
2361
|
};
|
|
1883
2362
|
}
|
|
1884
2363
|
readStream(path, options = {}) {
|
|
1885
|
-
return
|
|
1886
|
-
[Symbol.asyncIterator]: () => {
|
|
1887
|
-
let initialized = false;
|
|
1888
|
-
let finished = false;
|
|
1889
|
-
let offset = 0;
|
|
1890
|
-
let end = 0;
|
|
1891
|
-
let chunkSize = preferredIoBlockSize;
|
|
1892
|
-
let node;
|
|
1893
|
-
let allocation;
|
|
1894
|
-
let data;
|
|
1895
|
-
const release = () => {
|
|
1896
|
-
if (finished)
|
|
1897
|
-
return;
|
|
1898
|
-
finished = true;
|
|
1899
|
-
if (allocation) {
|
|
1900
|
-
allocation.release();
|
|
1901
|
-
allocation = undefined;
|
|
1902
|
-
}
|
|
1903
|
-
if (node) {
|
|
1904
|
-
const releasedNode = node;
|
|
1905
|
-
node = undefined;
|
|
1906
|
-
this.releaseReference(releasedNode, path);
|
|
1907
|
-
}
|
|
1908
|
-
data = undefined;
|
|
1909
|
-
};
|
|
1910
|
-
const step = () => {
|
|
1911
|
-
if (finished)
|
|
1912
|
-
return { done: true, value: undefined };
|
|
1913
|
-
try {
|
|
1914
|
-
options.signal?.throwIfAborted();
|
|
1915
|
-
if (!initialized) {
|
|
1916
|
-
initialized = true;
|
|
1917
|
-
const start = options.start ?? 0;
|
|
1918
|
-
chunkSize = options.chunkSize ?? preferredIoBlockSize;
|
|
1919
|
-
this.integer(start, "readStream", path);
|
|
1920
|
-
this.integer(chunkSize, "readStream", path);
|
|
1921
|
-
if (chunkSize === 0)
|
|
1922
|
-
this.fail("EINVAL", "readStream", path);
|
|
1923
|
-
if (options.endExclusive !== undefined) {
|
|
1924
|
-
this.integer(options.endExclusive, "readStream", path);
|
|
1925
|
-
if (options.endExclusive < start)
|
|
1926
|
-
this.fail("EINVAL", "readStream", path);
|
|
1927
|
-
}
|
|
1928
|
-
const target = this.file(path, "readStream");
|
|
1929
|
-
this.permission(target, 4, "readStream", path);
|
|
1930
|
-
this.ledger.reserve(path.length * 2, 1, "readStream", path);
|
|
1931
|
-
target.references++;
|
|
1932
|
-
node = target;
|
|
1933
|
-
allocation = target.allocation;
|
|
1934
|
-
allocation.retain();
|
|
1935
|
-
data = target.data;
|
|
1936
|
-
offset = start;
|
|
1937
|
-
end = Math.min(options.endExclusive ?? data.byteLength, data.byteLength);
|
|
1938
|
-
target.atimeMs = Date.now();
|
|
1939
|
-
}
|
|
1940
|
-
if (offset < end) {
|
|
1941
|
-
const nextEnd = Math.min(offset + chunkSize, end);
|
|
1942
|
-
const chunk = data.slice(offset, nextEnd);
|
|
1943
|
-
offset = nextEnd;
|
|
1944
|
-
return { done: false, value: chunk };
|
|
1945
|
-
}
|
|
1946
|
-
release();
|
|
1947
|
-
return { done: true, value: undefined };
|
|
1948
|
-
}
|
|
1949
|
-
catch (error) {
|
|
1950
|
-
release();
|
|
1951
|
-
throw error;
|
|
1952
|
-
}
|
|
1953
|
-
};
|
|
1954
|
-
return {
|
|
1955
|
-
abortSignal: options.signal,
|
|
1956
|
-
[Symbol.asyncIterator]() { return this; },
|
|
1957
|
-
tryNextSync: step,
|
|
1958
|
-
next() {
|
|
1959
|
-
try {
|
|
1960
|
-
return Promise.resolve(step());
|
|
1961
|
-
}
|
|
1962
|
-
catch (error) {
|
|
1963
|
-
return Promise.reject(error);
|
|
1964
|
-
}
|
|
1965
|
-
},
|
|
1966
|
-
return() {
|
|
1967
|
-
release();
|
|
1968
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
1969
|
-
},
|
|
1970
|
-
};
|
|
1971
|
-
},
|
|
1972
|
-
};
|
|
2364
|
+
return new MemoryReadStream(this, path, options);
|
|
1973
2365
|
}
|
|
1974
2366
|
async writeStream(path, source, options = {}) {
|
|
1975
2367
|
options.signal?.throwIfAborted();
|
|
@@ -2075,6 +2467,90 @@ const writeFileFastMethodNames = [
|
|
|
2075
2467
|
"addNode", "replaceData", "resolve", "permission", "validatePath", "mode",
|
|
2076
2468
|
"bytes", "allocate", "admitSize", "changed", "metadata", "fail",
|
|
2077
2469
|
];
|
|
2470
|
+
const mkdirFastMethodNames = [
|
|
2471
|
+
"mkdir", "lstat", "stat", "resolve", "permission", "validatePath", "mode",
|
|
2472
|
+
"addNode", "directory", "changed", "fail",
|
|
2473
|
+
];
|
|
2474
|
+
const rmFastMethodNames = [
|
|
2475
|
+
"rm", "lstat", "stat", "readdir", "entry", "resolve", "removeLocation",
|
|
2476
|
+
"permission", "validatePath", "terminalDot", "releaseNode", "changed", "fail",
|
|
2477
|
+
];
|
|
2478
|
+
const openRedirectFastMethodNames = [
|
|
2479
|
+
"open", "writeFile", "appendFile", "rename", "writeData", "prepareWrite", "openWrite",
|
|
2480
|
+
"resizeNode", "writeAt", "addNode", "replaceData", "resolve", "permission",
|
|
2481
|
+
"validatePath", "mode", "bytes", "allocate", "admitSize", "changed", "metadata",
|
|
2482
|
+
"releaseReference", "releaseNode", "fail",
|
|
2483
|
+
];
|
|
2484
|
+
export class MemoryRedirectHandle {
|
|
2485
|
+
constructor(fs, path, node, append) {
|
|
2486
|
+
this.fs = fs;
|
|
2487
|
+
this.path = path;
|
|
2488
|
+
this.node = node;
|
|
2489
|
+
this.append = append;
|
|
2490
|
+
this.position = 0;
|
|
2491
|
+
this.closed = false;
|
|
2492
|
+
}
|
|
2493
|
+
writeSync(chunk, signal) {
|
|
2494
|
+
signal?.throwIfAborted();
|
|
2495
|
+
if (this.closed)
|
|
2496
|
+
throw new FsError("EBADF", { syscall: "write", path: this.path });
|
|
2497
|
+
if (chunk.byteLength === 0)
|
|
2498
|
+
return;
|
|
2499
|
+
const pos = this.append ? this.node.byteLength : this.position;
|
|
2500
|
+
this.fs.writeAt(this.node, chunk, pos, "write", this.path);
|
|
2501
|
+
if (!this.append)
|
|
2502
|
+
this.position = pos + chunk.byteLength;
|
|
2503
|
+
}
|
|
2504
|
+
close() {
|
|
2505
|
+
if (this.closed)
|
|
2506
|
+
return;
|
|
2507
|
+
this.closed = true;
|
|
2508
|
+
this.fs.releaseReference(this.node, this.path);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
export function tryOpenMemoryRedirectHandleSync(filesystem, path, append, mode, signal) {
|
|
2512
|
+
const mem = filesystem;
|
|
2513
|
+
const owner = ownedStores.get(mem);
|
|
2514
|
+
if (!owner ||
|
|
2515
|
+
mem.symlinkCount !== 0 ||
|
|
2516
|
+
mem.capabilities !== owner.capabilities ||
|
|
2517
|
+
activeConditionalMutations.has(mem.identityScope) ||
|
|
2518
|
+
!isStockMemoryMethods(mem, openRedirectFastMethodNames, false) ||
|
|
2519
|
+
!isCleanAbsolutePath(path) ||
|
|
2520
|
+
path === "/dev" ||
|
|
2521
|
+
path.startsWith("/dev/")) {
|
|
2522
|
+
return undefined;
|
|
2523
|
+
}
|
|
2524
|
+
signal?.throwIfAborted();
|
|
2525
|
+
const validMode = mem.mode(mode, 0o666, "open", path);
|
|
2526
|
+
const location = mem.resolve(path, "open", {
|
|
2527
|
+
followFinal: true,
|
|
2528
|
+
allowMissing: true,
|
|
2529
|
+
});
|
|
2530
|
+
let node = location.node;
|
|
2531
|
+
if (node) {
|
|
2532
|
+
if (node.type !== "file")
|
|
2533
|
+
mem.fail("EISDIR", "open", path);
|
|
2534
|
+
mem.permission(node, 2, "open", path);
|
|
2535
|
+
}
|
|
2536
|
+
else {
|
|
2537
|
+
mem.permission(location.parent, 3, "open", path);
|
|
2538
|
+
}
|
|
2539
|
+
const ledger = mem.ledger;
|
|
2540
|
+
ledger.reserve(path.length * 2, 1, "open", path);
|
|
2541
|
+
try {
|
|
2542
|
+
node ??= mem.openWrite(path, {}, "open", { location, mode: validMode, append: false });
|
|
2543
|
+
if (!append) {
|
|
2544
|
+
mem.resizeNode(node, 0, "open", path);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
catch (error) {
|
|
2548
|
+
ledger.release(path.length * 2, 1);
|
|
2549
|
+
throw error;
|
|
2550
|
+
}
|
|
2551
|
+
node.references++;
|
|
2552
|
+
return new MemoryRedirectHandle(mem, path, node, append);
|
|
2553
|
+
}
|
|
2078
2554
|
export function utf8ByteLength(value) {
|
|
2079
2555
|
let bytes = 0;
|
|
2080
2556
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -2255,3 +2731,75 @@ export function tryWriteMemoryFileSync(filesystem, path, data, append, mode, sig
|
|
|
2255
2731
|
mem.writeMemoryFileFast(path, data, append, mode);
|
|
2256
2732
|
return true;
|
|
2257
2733
|
}
|
|
2734
|
+
export function tryMkdirMemorySync(filesystem, path, recursive, mode, signal) {
|
|
2735
|
+
const mem = filesystem;
|
|
2736
|
+
const owner = ownedStores.get(mem);
|
|
2737
|
+
if (!owner ||
|
|
2738
|
+
mem.symlinkCount !== 0 ||
|
|
2739
|
+
mem.capabilities !== owner.capabilities ||
|
|
2740
|
+
!isStockMemoryMethods(mem, mkdirFastMethodNames, false) ||
|
|
2741
|
+
!isCleanAbsolutePath(path) ||
|
|
2742
|
+
path === "/dev" ||
|
|
2743
|
+
path.startsWith("/dev/")) {
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
signal?.throwIfAborted();
|
|
2747
|
+
const validMode = mem.mode(mode, 0o777, "mkdir", path);
|
|
2748
|
+
if (recursive) {
|
|
2749
|
+
const node = mem.resolve(path, "mkdir", { createDirectories: validMode }).node;
|
|
2750
|
+
if (node.type !== "directory")
|
|
2751
|
+
mem.fail("EEXIST", "mkdir", path);
|
|
2752
|
+
return true;
|
|
2753
|
+
}
|
|
2754
|
+
const location = mem.resolve(path, "mkdir", { allowMissing: true, followFinal: false });
|
|
2755
|
+
if (location.node)
|
|
2756
|
+
mem.fail("EEXIST", "mkdir", path);
|
|
2757
|
+
mem.permission(location.parent, 3, "mkdir", path);
|
|
2758
|
+
mem.addDirectoryNode(location.parent, location.name, validMode, "mkdir", path);
|
|
2759
|
+
return true;
|
|
2760
|
+
}
|
|
2761
|
+
export function tryRmRfMemorySync(filesystem, path, signal) {
|
|
2762
|
+
const mem = filesystem;
|
|
2763
|
+
const owner = ownedStores.get(mem);
|
|
2764
|
+
if (!owner ||
|
|
2765
|
+
mem.symlinkCount !== 0 ||
|
|
2766
|
+
mem.capabilities !== owner.capabilities ||
|
|
2767
|
+
activeConditionalMutations.has(mem.identityScope) ||
|
|
2768
|
+
!isStockMemoryMethods(mem, rmFastMethodNames, false) ||
|
|
2769
|
+
!isCleanAbsolutePath(path) ||
|
|
2770
|
+
path === "/dev" ||
|
|
2771
|
+
path.startsWith("/dev/")) {
|
|
2772
|
+
return false;
|
|
2773
|
+
}
|
|
2774
|
+
signal?.throwIfAborted();
|
|
2775
|
+
let location;
|
|
2776
|
+
try {
|
|
2777
|
+
location = mem.resolve(path, "rm", noFollowResolveOptions);
|
|
2778
|
+
}
|
|
2779
|
+
catch (error) {
|
|
2780
|
+
if (error instanceof FsError && error.code === "ENOENT")
|
|
2781
|
+
return true;
|
|
2782
|
+
throw error;
|
|
2783
|
+
}
|
|
2784
|
+
mem.removeLocation(location, path, "rm", true);
|
|
2785
|
+
return true;
|
|
2786
|
+
}
|
|
2787
|
+
export function tryWriteMemoryFileInDirSync(filesystem, dirPrefix, name, data, append, mode, signal) {
|
|
2788
|
+
const mem = filesystem;
|
|
2789
|
+
const owner = ownedStores.get(mem);
|
|
2790
|
+
if (!owner ||
|
|
2791
|
+
mem.symlinkCount !== 0 ||
|
|
2792
|
+
mem.capabilities !== owner.capabilities ||
|
|
2793
|
+
!isStockMemoryMethods(mem, writeFileFastMethodNames, false) ||
|
|
2794
|
+
name.length === 0 ||
|
|
2795
|
+
name === "." ||
|
|
2796
|
+
name === ".." ||
|
|
2797
|
+
name.indexOf("/") !== -1 ||
|
|
2798
|
+
name.indexOf("\0") !== -1 ||
|
|
2799
|
+
dirPrefix.length + name.length > 65536) {
|
|
2800
|
+
return false;
|
|
2801
|
+
}
|
|
2802
|
+
signal?.throwIfAborted();
|
|
2803
|
+
mem.writeMemoryFileInDirFast(dirPrefix, name, data, append, mode);
|
|
2804
|
+
return true;
|
|
2805
|
+
}
|