@poe-platform/safe-fs 0.1.804 → 0.1.806
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 -5
- package/dist/safe-fs/bridge/filesystem.d.ts +1 -0
- package/dist/safe-fs/bridge/filesystem.js +3 -0
- package/dist/safe-fs/bridge/index.d.ts +1 -0
- package/dist/safe-fs/bridge/index.js +1 -0
- package/dist/safe-fs/config/registry.d.ts +2 -0
- package/dist/safe-fs/config/registry.js +15 -0
- package/dist/safe-fs/config.node.js +3 -14
- package/dist/safe-fs/config.portable.d.ts +3 -0
- package/dist/safe-fs/config.portable.js +9 -0
- package/dist/safe-fs/contracts/errors.d.ts +1 -0
- package/dist/safe-fs/contracts/errors.js +12 -0
- package/dist/safe-fs/contracts/filesystem.d.ts +12 -0
- package/dist/safe-fs/contracts/index.d.ts +1 -0
- package/dist/safe-fs/contracts/index.js +1 -0
- package/dist/safe-fs/contracts/io.js +1 -1
- package/dist/safe-fs/core.d.ts +5 -14
- package/dist/safe-fs/core.js +4 -11
- package/dist/safe-fs/fs/devices/index.d.ts +8 -2
- package/dist/safe-fs/fs/devices/index.js +79 -23
- package/dist/safe-fs/fs/devices/path.js +1 -1
- package/dist/safe-fs/fs/memory/atomic-view.d.ts +3 -0
- package/dist/safe-fs/fs/memory/atomic-view.js +5 -0
- package/dist/safe-fs/fs/memory/index.d.ts +7 -4
- package/dist/safe-fs/fs/memory/index.js +211 -160
- package/dist/safe-fs/fs/memory/ledger.d.ts +0 -4
- package/dist/safe-fs/fs/memory/ledger.js +0 -15
- package/dist/safe-fs/fs/memory/limits.js +3 -0
- package/dist/safe-fs/fs/mount/comparison.js +3 -3
- package/dist/safe-fs/fs/mount/identity.d.ts +4 -0
- package/dist/safe-fs/fs/mount/identity.js +8 -0
- package/dist/safe-fs/fs/mount/index.d.ts +1 -0
- package/dist/safe-fs/fs/mount/index.js +31 -0
- package/dist/safe-fs/fs/object-publication/index.d.ts +3 -0
- package/dist/safe-fs/fs/object-publication/index.js +3 -2
- package/dist/safe-fs/fs/quota/index.js +1 -1
- package/dist/safe-fs/fs/readonly/index.js +1 -1
- package/dist/safe-fs/fs/s3/filesystem.js +1 -1
- package/dist/safe-fs/fs/s3/http/transport.js +1 -1
- package/dist/safe-fs/fs/s3/http/xml.js +2 -2
- package/dist/safe-fs/fs/scoped.js +32 -2
- package/dist/safe-fs/fs/webdav/webdav.js +1 -1
- package/dist/safe-fs/index.d.ts +2 -0
- package/dist/safe-fs/index.js +2 -0
- package/dist/safe-fs/node/filesystem.d.ts +2 -1
- package/dist/safe-fs/node/filesystem.js +1 -5
- package/dist/safe-fs/runtime-core.d.ts +27 -0
- package/dist/safe-fs/runtime-core.js +57 -0
- package/package.json +9 -1
|
@@ -9,7 +9,7 @@ import { directoryAncestryPaths, runStagingGuard, snapshotDirectoryAncestry, sna
|
|
|
9
9
|
import { compareIdentity } from "../mount/identity.js";
|
|
10
10
|
import { createStagingCleanup, snapshotStagingCreation } from "../staging-cleanup.js";
|
|
11
11
|
import { resolveMissingTarget } from "./missing-target.js";
|
|
12
|
-
import {
|
|
12
|
+
import { registerLazyMemoryAtomicView } from "./atomic-view.js";
|
|
13
13
|
import { snapshotConditionalChmod } from "../conditional-chmod.js";
|
|
14
14
|
import { MemoryAllocation, MemoryLedger } from "./ledger.js";
|
|
15
15
|
import { normalizeMemoryFileSystemLimits } from "./limits.js";
|
|
@@ -50,14 +50,17 @@ const DUMMY_POOL_LEDGER = new MemoryLedger(normalizeMemoryFileSystemLimits({}));
|
|
|
50
50
|
const DUMMY_POOL_ALLOCATION = new MemoryAllocation(EMPTY_ALLOC_BYTES, DUMMY_POOL_LEDGER);
|
|
51
51
|
const DUMMY_POOL_FILE_NODE = new MemoryFileNode(0, 0, null, 0, DUMMY_POOL_ALLOCATION, DUMMY_POOL_ALLOCATION.data);
|
|
52
52
|
DUMMY_POOL_FILE_NODE.atimeMs = DUMMY_POOL_FILE_NODE.mtimeMs = DUMMY_POOL_FILE_NODE.ctimeMs = DUMMY_POOL_FILE_NODE.birthtimeMs = 1700000000000;
|
|
53
|
+
const EMPTY_DIR_TABLE = new Int16Array(0);
|
|
54
|
+
const EMPTY_DIR_KEYS = [];
|
|
55
|
+
const EMPTY_DIR_VALS = [];
|
|
53
56
|
class FastDirectoryEntriesMap {
|
|
54
57
|
// Keep speculative slots local so a failed write cannot pin a tenant globally.
|
|
55
58
|
_missKey = "";
|
|
56
59
|
_missSlot = -1;
|
|
57
|
-
_table =
|
|
58
|
-
_mask =
|
|
59
|
-
_keys =
|
|
60
|
-
_vals =
|
|
60
|
+
_table = EMPTY_DIR_TABLE;
|
|
61
|
+
_mask = 0;
|
|
62
|
+
_keys = EMPTY_DIR_KEYS;
|
|
63
|
+
_vals = EMPTY_DIR_VALS;
|
|
61
64
|
_next = 0;
|
|
62
65
|
size = 0;
|
|
63
66
|
[Symbol.toStringTag] = "Map";
|
|
@@ -90,6 +93,11 @@ class FastDirectoryEntriesMap {
|
|
|
90
93
|
getForWriteWithHash(k, h) {
|
|
91
94
|
let slot = h & this._mask;
|
|
92
95
|
if (this.size === 0) {
|
|
96
|
+
if (this._keys.length === 0) {
|
|
97
|
+
this._missKey = "";
|
|
98
|
+
this._missSlot = -1;
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
93
101
|
this._missKey = k;
|
|
94
102
|
this._missSlot = slot;
|
|
95
103
|
return undefined;
|
|
@@ -205,7 +213,7 @@ class FastDirectoryEntriesMap {
|
|
|
205
213
|
return this;
|
|
206
214
|
}
|
|
207
215
|
if (this._next >= this._keys.length) {
|
|
208
|
-
this._rebuild(this.size * 2 <= this._keys.length ? this._keys.length : this._keys.length * 2);
|
|
216
|
+
this._rebuild(this._keys.length === 0 ? 8 : (this.size * 2 <= this._keys.length ? this._keys.length : this._keys.length * 2));
|
|
209
217
|
}
|
|
210
218
|
let slot = this._hash(k);
|
|
211
219
|
const mask = this._mask;
|
|
@@ -316,13 +324,6 @@ class FastDirectoryEntriesMap {
|
|
|
316
324
|
}
|
|
317
325
|
const SHARED_POOL_CAPACITY = 256;
|
|
318
326
|
const SHARED_DIR_POOL_CAPACITY = 64;
|
|
319
|
-
const sharedAllocationPool = new Array(SHARED_POOL_CAPACITY).fill(DUMMY_POOL_ALLOCATION);
|
|
320
|
-
let sharedAllocationPoolLen = 0;
|
|
321
|
-
const SHARED_LARGE_POOL_CAPACITY = 8;
|
|
322
|
-
const sharedLargeAllocationPool = new Array(SHARED_LARGE_POOL_CAPACITY).fill(DUMMY_POOL_ALLOCATION);
|
|
323
|
-
let sharedLargeAllocationPoolLen = 0;
|
|
324
|
-
const sharedMediumAllocationPool = new Array(SHARED_LARGE_POOL_CAPACITY).fill(DUMMY_POOL_ALLOCATION);
|
|
325
|
-
let sharedMediumAllocationPoolLen = 0;
|
|
326
327
|
const sharedFileNodePool = new Array(SHARED_POOL_CAPACITY).fill(DUMMY_POOL_FILE_NODE);
|
|
327
328
|
let sharedFileNodePoolLen = 0;
|
|
328
329
|
const sharedDirectoryNodePool = new Array(SHARED_DIR_POOL_CAPACITY);
|
|
@@ -343,27 +344,9 @@ function collectRemovedChild(child, name) {
|
|
|
343
344
|
sharedRemovedKeyEntries++;
|
|
344
345
|
}
|
|
345
346
|
function replenishSharedMemoryPools() {
|
|
346
|
-
while (sharedAllocationPoolLen < 112) {
|
|
347
|
-
DUMMY_POOL_LEDGER.reserve(64, 0, "init", "/");
|
|
348
|
-
const alloc = new MemoryAllocation(new Uint8Array(64), DUMMY_POOL_LEDGER);
|
|
349
|
-
alloc.release();
|
|
350
|
-
sharedAllocationPool[sharedAllocationPoolLen++] = alloc;
|
|
351
|
-
}
|
|
352
347
|
while (sharedFileNodePoolLen < 112) {
|
|
353
348
|
sharedFileNodePool[sharedFileNodePoolLen++] = new MemoryFileNode(0, 0, 0, 0, DUMMY_POOL_ALLOCATION, undefined);
|
|
354
349
|
}
|
|
355
|
-
while (sharedLargeAllocationPoolLen < 4) {
|
|
356
|
-
DUMMY_POOL_LEDGER.reserve(65536, 0, "init", "/");
|
|
357
|
-
const alloc = new MemoryAllocation(new Uint8Array(65536), DUMMY_POOL_LEDGER);
|
|
358
|
-
alloc.release();
|
|
359
|
-
sharedLargeAllocationPool[sharedLargeAllocationPoolLen++] = alloc;
|
|
360
|
-
}
|
|
361
|
-
while (sharedMediumAllocationPoolLen < 4) {
|
|
362
|
-
DUMMY_POOL_LEDGER.reserve(16384, 0, "init", "/");
|
|
363
|
-
const alloc = new MemoryAllocation(new Uint8Array(16384), DUMMY_POOL_LEDGER);
|
|
364
|
-
alloc.release();
|
|
365
|
-
sharedMediumAllocationPool[sharedMediumAllocationPoolLen++] = alloc;
|
|
366
|
-
}
|
|
367
350
|
while (sharedDirectoryNodePoolLen < 16) {
|
|
368
351
|
sharedDirectoryNodePool[sharedDirectoryNodePoolLen++] = new MemoryDirectoryNode(0, 0, 0);
|
|
369
352
|
}
|
|
@@ -395,7 +378,6 @@ Object.assign(MemoryDirectoryNode.prototype, {
|
|
|
395
378
|
const DUMMY_POOL_DIR_NODE = new MemoryDirectoryNode(0, 0, null);
|
|
396
379
|
DUMMY_POOL_DIR_NODE.atimeMs = DUMMY_POOL_DIR_NODE.mtimeMs = DUMMY_POOL_DIR_NODE.ctimeMs = DUMMY_POOL_DIR_NODE.birthtimeMs = 1700000000000;
|
|
397
380
|
class MemoryCache {
|
|
398
|
-
allocations = [];
|
|
399
381
|
files = [];
|
|
400
382
|
directories = [];
|
|
401
383
|
removedScratch = [];
|
|
@@ -415,7 +397,6 @@ class MemoryCache {
|
|
|
415
397
|
const typeModes = { file: 0o100000, directory: 0o040000, symlink: 0o120000 };
|
|
416
398
|
const emptyResolveOptions = Object.freeze({});
|
|
417
399
|
const noFollowResolveOptions = Object.freeze({ followFinal: false });
|
|
418
|
-
const sharedMkdirResolveOptions = { createDirectories: 0o777 };
|
|
419
400
|
const sharedAllowMissingNoFollowOptions = Object.freeze({ allowMissing: true, followFinal: false });
|
|
420
401
|
const sharedFastLocation = { node: undefined, parent: DUMMY_POOL_DIR_NODE, name: "", path: "" };
|
|
421
402
|
const resolvedVoid = Promise.resolve();
|
|
@@ -590,32 +571,48 @@ class MemoryReadStream {
|
|
|
590
571
|
return STREAM_RESOLVED_DONE;
|
|
591
572
|
}
|
|
592
573
|
}
|
|
574
|
+
const SHARED_STOCK_MEMORY_CAPABILITIES = Object.freeze({
|
|
575
|
+
read: true, stat: true, readdir: true, realpath: true, access: true, open: true,
|
|
576
|
+
write: true, append: true, exclusiveCreate: true, explicitDirectories: true, implicitDirectories: false,
|
|
577
|
+
mkdir: true, recursiveMkdir: true, remove: true, removeDirectory: true, recursiveRemove: true,
|
|
578
|
+
rename: true, atomicRenameNoReplace: true, copy: true, exclusiveCopy: true, readlink: true, truncate: true,
|
|
579
|
+
streamingAppend: true, randomAccessWrite: true,
|
|
580
|
+
readOnly: false,
|
|
581
|
+
symlinks: true, conditionalChmod: true,
|
|
582
|
+
hardlinks: true,
|
|
583
|
+
permissions: true,
|
|
584
|
+
timestamps: true,
|
|
585
|
+
atomicRename: true,
|
|
586
|
+
atomicFileStaging: true, retainedStagingCleanup: true, retainedStagingWrite: true, atomicStagingAncestry: true, atomicFileMutation: true, atomicEntryRemoval: true, atomicEntryRemovalReceipt: true, atomicTreeRemoval: true,
|
|
587
|
+
synchronousDirectoryValidation: true, synchronousStagingResolution: true, guardedStagingPublication: true,
|
|
588
|
+
atomicDirectoryMetadata: true,
|
|
589
|
+
streamingRead: true,
|
|
590
|
+
retainedRead: true,
|
|
591
|
+
retainedResize: true,
|
|
592
|
+
streamingWrite: true,
|
|
593
|
+
descriptorWriteStream: true,
|
|
594
|
+
});
|
|
595
|
+
function customMemoryCapabilities(filesystem) {
|
|
596
|
+
return Object.freeze({
|
|
597
|
+
...SHARED_STOCK_MEMORY_CAPABILITIES,
|
|
598
|
+
get retainedResize() { return stockRetainedResize(filesystem); },
|
|
599
|
+
get descriptorWriteStream() { return stockDescriptorWrite(filesystem); },
|
|
600
|
+
});
|
|
601
|
+
}
|
|
593
602
|
export class MemoryFileSystem {
|
|
594
603
|
capabilitiesFor;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
atomicRename: true,
|
|
608
|
-
atomicFileStaging: true, retainedStagingCleanup: true, retainedStagingWrite: true, atomicStagingAncestry: true, atomicFileMutation: true, atomicEntryRemoval: true, atomicEntryRemovalReceipt: true, atomicTreeRemoval: true,
|
|
609
|
-
synchronousDirectoryValidation: true, synchronousStagingResolution: true, guardedStagingPublication: true,
|
|
610
|
-
atomicDirectoryMetadata: true,
|
|
611
|
-
streamingRead: true,
|
|
612
|
-
retainedRead: true,
|
|
613
|
-
get retainedResize() { return stockRetainedResize(filesystem); },
|
|
614
|
-
streamingWrite: true,
|
|
615
|
-
get descriptorWriteStream() { return stockDescriptorWrite(filesystem); },
|
|
616
|
-
});
|
|
617
|
-
})(this);
|
|
618
|
-
identityScope = Symbol();
|
|
604
|
+
#customCapabilities;
|
|
605
|
+
#identityScopeSym;
|
|
606
|
+
get identityScope() {
|
|
607
|
+
return (this.#identityScopeSym ??= Symbol());
|
|
608
|
+
}
|
|
609
|
+
get capabilities() {
|
|
610
|
+
if (!this.#customCapabilities) {
|
|
611
|
+
this.#customCapabilities = customMemoryCapabilities(this);
|
|
612
|
+
this._owner.capabilities = this.#customCapabilities;
|
|
613
|
+
}
|
|
614
|
+
return this.#customCapabilities;
|
|
615
|
+
}
|
|
619
616
|
nextInode = 1;
|
|
620
617
|
ledger;
|
|
621
618
|
_cache;
|
|
@@ -624,26 +621,34 @@ export class MemoryFileSystem {
|
|
|
624
621
|
root;
|
|
625
622
|
totalBytes = 0;
|
|
626
623
|
symlinkCount = 0;
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
624
|
+
#atomicView;
|
|
625
|
+
_getAtomicView() {
|
|
626
|
+
const owner = ownedStores.get(this);
|
|
627
|
+
if (!owner ||
|
|
628
|
+
Object.getPrototypeOf(this) !== MemoryFileSystem.prototype ||
|
|
629
|
+
this.root !== owner.root ||
|
|
630
|
+
this.ledger !== owner.ledger ||
|
|
631
|
+
this.capabilities !== owner.capabilities) {
|
|
632
|
+
return undefined;
|
|
631
633
|
}
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
634
|
+
const proto = MemoryFileSystem.prototype;
|
|
635
|
+
for (let i = 0; i < memoryImplementationKeys.length; i++) {
|
|
636
|
+
const name = memoryImplementationKeys[i];
|
|
637
|
+
const expected = memoryImplementationDescriptors[i];
|
|
638
|
+
if (name === "capabilities")
|
|
639
|
+
continue;
|
|
640
|
+
if (Object.prototype.hasOwnProperty.call(this, name)) {
|
|
641
|
+
const actual = Object.getOwnPropertyDescriptor(this, name);
|
|
642
|
+
if (!actual || actual.value !== expected.value || actual.get !== expected.get || actual.set !== expected.set)
|
|
643
|
+
return undefined;
|
|
644
|
+
}
|
|
645
|
+
else if (expected.get !== undefined || expected.set !== undefined || proto[name] !== expected.value) {
|
|
646
|
+
const actual = Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, name);
|
|
647
|
+
if (!actual || actual.value !== expected.value || actual.get !== expected.get || actual.set !== expected.set)
|
|
648
|
+
return undefined;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return this.#atomicView ??= {
|
|
647
652
|
stat: (path) => {
|
|
648
653
|
this.validatePath(path, "overlayAtomicView");
|
|
649
654
|
let node = this.root;
|
|
@@ -665,30 +670,28 @@ export class MemoryFileSystem {
|
|
|
665
670
|
this.fail("ENOTDIR", "overlayAtomicView", path);
|
|
666
671
|
return [...node.entries.keys()];
|
|
667
672
|
},
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
return true;
|
|
691
|
-
});
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
constructor(options = {}) {
|
|
676
|
+
this.ledger = new MemoryLedger(normalizeMemoryFileSystemLimits(options));
|
|
677
|
+
if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes) {
|
|
678
|
+
replenishSharedMemoryPools();
|
|
679
|
+
}
|
|
680
|
+
const cache = new MemoryCache();
|
|
681
|
+
this._cache = cache;
|
|
682
|
+
memoryCaches.set(this.ledger, cache);
|
|
683
|
+
this.ledger.reserve(0, 1, "mkdir", "/");
|
|
684
|
+
this.root = this.directory(0o755);
|
|
685
|
+
const root = this.root;
|
|
686
|
+
const owner = {
|
|
687
|
+
root,
|
|
688
|
+
ledger: this.ledger,
|
|
689
|
+
capabilities: SHARED_STOCK_MEMORY_CAPABILITIES,
|
|
690
|
+
intact: () => this.root === root,
|
|
691
|
+
};
|
|
692
|
+
this._owner = owner;
|
|
693
|
+
ownedStores.set(this, owner);
|
|
694
|
+
registerLazyMemoryAtomicView(this, memoryImplementation._getAtomicView.value);
|
|
692
695
|
if (this.compareEntry === memoryImplementation.compareEntry?.value) {
|
|
693
696
|
registeredAuthorities.add(this);
|
|
694
697
|
registerEntryAuthority(this, compareOwnedMemory);
|
|
@@ -882,21 +885,6 @@ export class MemoryFileSystem {
|
|
|
882
885
|
node.sourceRef = undefined;
|
|
883
886
|
node.allocation = DUMMY_POOL_ALLOCATION;
|
|
884
887
|
alloc.release();
|
|
885
|
-
if (alloc.isReleased64()) {
|
|
886
|
-
alloc.detachLedger(DUMMY_POOL_LEDGER);
|
|
887
|
-
if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedAllocationPoolLen < SHARED_POOL_CAPACITY) {
|
|
888
|
-
sharedAllocationPool[sharedAllocationPoolLen++] = alloc;
|
|
889
|
-
}
|
|
890
|
-
else if (cache.allocations.length < 128) {
|
|
891
|
-
cache.allocations.push(alloc);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
else if (alloc.isReleased65536()) {
|
|
895
|
-
alloc.detachLedger(DUMMY_POOL_LEDGER);
|
|
896
|
-
if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedLargeAllocationPoolLen < SHARED_LARGE_POOL_CAPACITY) {
|
|
897
|
-
sharedLargeAllocationPool[sharedLargeAllocationPoolLen++] = alloc;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
888
|
if (sharedFileNodePoolLen < SHARED_POOL_CAPACITY) {
|
|
901
889
|
sharedFileNodePool[sharedFileNodePoolLen++] = node;
|
|
902
890
|
}
|
|
@@ -936,18 +924,6 @@ export class MemoryFileSystem {
|
|
|
936
924
|
node.view = length === allocation.data.byteLength ? allocation.data : undefined;
|
|
937
925
|
node.allocation = allocation;
|
|
938
926
|
previous.release();
|
|
939
|
-
if (previous.isReleased64()) {
|
|
940
|
-
previous.detachLedger(DUMMY_POOL_LEDGER);
|
|
941
|
-
if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedAllocationPoolLen < SHARED_POOL_CAPACITY) {
|
|
942
|
-
sharedAllocationPool[sharedAllocationPoolLen++] = previous;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
else if (previous.isReleased65536()) {
|
|
946
|
-
previous.detachLedger(DUMMY_POOL_LEDGER);
|
|
947
|
-
if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedLargeAllocationPoolLen < SHARED_LARGE_POOL_CAPACITY) {
|
|
948
|
-
sharedLargeAllocationPool[sharedLargeAllocationPoolLen++] = previous;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
927
|
}
|
|
952
928
|
admitSize(node, length, syscall, path) {
|
|
953
929
|
this.ledger.fileSize(length, syscall, path);
|
|
@@ -1325,36 +1301,7 @@ export class MemoryFileSystem {
|
|
|
1325
1301
|
if (length === 0) {
|
|
1326
1302
|
return DUMMY_POOL_ALLOCATION;
|
|
1327
1303
|
}
|
|
1328
|
-
|
|
1329
|
-
if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedMediumAllocationPoolLen > 0) {
|
|
1330
|
-
const pooled = sharedMediumAllocationPool[--sharedMediumAllocationPoolLen];
|
|
1331
|
-
sharedMediumAllocationPool[sharedMediumAllocationPoolLen] = DUMMY_POOL_ALLOCATION;
|
|
1332
|
-
pooled.reuse(this.ledger);
|
|
1333
|
-
return pooled;
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
if (length === 65536) {
|
|
1337
|
-
if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedLargeAllocationPoolLen > 0) {
|
|
1338
|
-
const pooled = sharedLargeAllocationPool[--sharedLargeAllocationPoolLen];
|
|
1339
|
-
sharedLargeAllocationPool[sharedLargeAllocationPoolLen] = DUMMY_POOL_ALLOCATION;
|
|
1340
|
-
pooled.reuse(this.ledger);
|
|
1341
|
-
return pooled;
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
if (length === 64) {
|
|
1345
|
-
let pooled;
|
|
1346
|
-
if (this.ledger.hasInfiniteRetained && this.ledger.hasInfiniteFileBytes && sharedAllocationPoolLen > 0) {
|
|
1347
|
-
pooled = sharedAllocationPool[--sharedAllocationPoolLen];
|
|
1348
|
-
sharedAllocationPool[sharedAllocationPoolLen] = DUMMY_POOL_ALLOCATION;
|
|
1349
|
-
}
|
|
1350
|
-
else {
|
|
1351
|
-
pooled = memoryCaches.get(this.ledger).allocations.pop();
|
|
1352
|
-
}
|
|
1353
|
-
if (pooled) {
|
|
1354
|
-
pooled.reuse(this.ledger);
|
|
1355
|
-
return pooled;
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1304
|
+
// Views can outlive files: never recycle their backing buffers across owners.
|
|
1358
1305
|
try {
|
|
1359
1306
|
return new MemoryAllocation(new Uint8Array(length), this.ledger);
|
|
1360
1307
|
}
|
|
@@ -2300,6 +2247,93 @@ export class MemoryFileSystem {
|
|
|
2300
2247
|
this.fail("EAGAIN", "publishStagedFile", destination);
|
|
2301
2248
|
return MemoryFileSystem.prototype.rename.call(this, staging.file.path, destination, { ...controls, noReplace: expected === null });
|
|
2302
2249
|
}
|
|
2250
|
+
async publishStagedFileSet(receipt, destination, options) {
|
|
2251
|
+
const signal = options.signal;
|
|
2252
|
+
const guard = options.commitGuard;
|
|
2253
|
+
const ancestors = options.ancestors === undefined ? undefined : snapshotDirectoryAncestry(options.ancestors);
|
|
2254
|
+
const snapshot = (entry) => ({ path: entry.path, stat: { ...entry.stat } });
|
|
2255
|
+
const staging = { parent: snapshot(receipt.parent), directory: snapshot(receipt.directory), file: snapshot(receipt.file) };
|
|
2256
|
+
const parent = { ...options.parent };
|
|
2257
|
+
const expected = options.destination === null ? null : { ...options.destination };
|
|
2258
|
+
const companions = options.companions.map(entry => ({ path: entry.path, expected: entry.expected === null ? null : { ...entry.expected }, remove: entry.remove }));
|
|
2259
|
+
const syscall = "publishStagedFileSet";
|
|
2260
|
+
const parentPath = destination.slice(0, destination.lastIndexOf("/")) || "/";
|
|
2261
|
+
const paths = new Set([destination]);
|
|
2262
|
+
const canonical = (path) => path.startsWith("/") && path.slice(1).split("/").every(part => part !== "" && part !== "." && part !== "..");
|
|
2263
|
+
if (!canonical(destination) || ancestors && ancestors.at(-1)?.path !== parentPath)
|
|
2264
|
+
this.fail("EINVAL", syscall, destination);
|
|
2265
|
+
for (const entry of companions) {
|
|
2266
|
+
if (!canonical(entry.path) || paths.has(entry.path) || (entry.path.slice(0, entry.path.lastIndexOf("/")) || "/") !== parentPath)
|
|
2267
|
+
this.fail("EINVAL", syscall, entry.path);
|
|
2268
|
+
paths.add(entry.path);
|
|
2269
|
+
}
|
|
2270
|
+
signal?.throwIfAborted();
|
|
2271
|
+
if (guard !== undefined)
|
|
2272
|
+
runStagingGuard(guard);
|
|
2273
|
+
signal?.throwIfAborted();
|
|
2274
|
+
if (ancestors)
|
|
2275
|
+
this.verifyDirectoryAncestry(ancestors, signal === undefined ? {} : { signal });
|
|
2276
|
+
const { directory, file } = this.stagingLocations(staging);
|
|
2277
|
+
this.expectEntry(file.node, staging.file.stat, staging.file.path);
|
|
2278
|
+
const target = this.entry(destination, syscall, true);
|
|
2279
|
+
this.expectEntry(target.parent, parent, destination, false);
|
|
2280
|
+
this.expectEntry(target.node, expected, destination);
|
|
2281
|
+
const node = file.node;
|
|
2282
|
+
if (node.type !== "file" || target.parent === directory.node || target.node === node)
|
|
2283
|
+
this.fail("EINVAL", syscall, destination);
|
|
2284
|
+
if (target.node && target.node.type !== "file")
|
|
2285
|
+
this.fail("EAGAIN", syscall, destination);
|
|
2286
|
+
this.permission(file.parent, 3, syscall, staging.file.path);
|
|
2287
|
+
this.permission(target.parent, 3, syscall, destination);
|
|
2288
|
+
const retired = [];
|
|
2289
|
+
for (const companion of companions) {
|
|
2290
|
+
const location = this.entry(companion.path, syscall, true);
|
|
2291
|
+
if (location.parent !== target.parent || location.node === node)
|
|
2292
|
+
this.fail("EINVAL", syscall, companion.path);
|
|
2293
|
+
this.expectEntry(location.node, companion.expected, companion.path);
|
|
2294
|
+
if (location.node && location.node.type !== "file")
|
|
2295
|
+
this.fail("EAGAIN", syscall, companion.path);
|
|
2296
|
+
if (companion.remove && location.node)
|
|
2297
|
+
retired.push(location);
|
|
2298
|
+
}
|
|
2299
|
+
const now = Date.now();
|
|
2300
|
+
const published = { ...this.snapshot(node), ctimeMs: now, revision: Math.min(Number.MAX_SAFE_INTEGER + 1, node.revision + 1) };
|
|
2301
|
+
// Admission and all source-set checks precede the single synchronous commit.
|
|
2302
|
+
// No calls to overridable async mutations can interleave a partial result.
|
|
2303
|
+
const nameGrowth = target.node ? 0 : (target.name.length - file.name.length) * 2;
|
|
2304
|
+
this.ledger.reserve(Math.max(0, nameGrowth), 0, syscall, destination);
|
|
2305
|
+
try {
|
|
2306
|
+
target.parent.entries.set(target.name, node);
|
|
2307
|
+
}
|
|
2308
|
+
catch (error) {
|
|
2309
|
+
this.ledger.release(Math.max(0, nameGrowth), 0);
|
|
2310
|
+
throw error;
|
|
2311
|
+
}
|
|
2312
|
+
memoryCaches.get(this.ledger).clearWrites();
|
|
2313
|
+
file.parent.entries.delete(file.name);
|
|
2314
|
+
const unlink = (entry) => {
|
|
2315
|
+
entry.node.nlink--;
|
|
2316
|
+
entry.node.ctimeMs = now;
|
|
2317
|
+
entry.node.revision = Math.min(Number.MAX_SAFE_INTEGER + 1, entry.node.revision + 1);
|
|
2318
|
+
this.releaseNode(entry.node);
|
|
2319
|
+
};
|
|
2320
|
+
if (target.node) {
|
|
2321
|
+
this.ledger.release(file.name.length * 2, 1);
|
|
2322
|
+
unlink(target);
|
|
2323
|
+
}
|
|
2324
|
+
else if (nameGrowth < 0)
|
|
2325
|
+
this.ledger.release(-nameGrowth, 0);
|
|
2326
|
+
for (const entry of retired) {
|
|
2327
|
+
entry.parent.entries.delete(entry.name);
|
|
2328
|
+
this.ledger.release(entry.name.length * 2, 1);
|
|
2329
|
+
unlink(entry);
|
|
2330
|
+
}
|
|
2331
|
+
this.changed(file.parent, now);
|
|
2332
|
+
this.changed(target.parent, now);
|
|
2333
|
+
node.ctimeMs = now;
|
|
2334
|
+
node.revision = published.revision;
|
|
2335
|
+
return published;
|
|
2336
|
+
}
|
|
2303
2337
|
async removeStagedFile(staging, options = {}) {
|
|
2304
2338
|
options.signal?.throwIfAborted();
|
|
2305
2339
|
const { directory, file } = this.stagingLocations(staging);
|
|
@@ -2896,7 +2930,10 @@ export class MemoryFileSystem {
|
|
|
2896
2930
|
if (!descriptor || !("value" in descriptor) || descriptor.value !== memoryImplementation[name]?.value)
|
|
2897
2931
|
unsupported();
|
|
2898
2932
|
}
|
|
2899
|
-
const
|
|
2933
|
+
const capabilities = Object.getOwnPropertyDescriptor(this, "capabilities");
|
|
2934
|
+
const retainedRead = capabilities === undefined
|
|
2935
|
+
? Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, "capabilities")?.get === memoryImplementation.capabilities?.get
|
|
2936
|
+
: capabilities.value?.retainedRead;
|
|
2900
2937
|
signal?.throwIfAborted();
|
|
2901
2938
|
if (retainedRead !== true)
|
|
2902
2939
|
unsupported();
|
|
@@ -3036,7 +3073,7 @@ function stockDescriptorWrite(filesystem) {
|
|
|
3036
3073
|
return false;
|
|
3037
3074
|
const owner = ownedStores.get(filesystem);
|
|
3038
3075
|
const capDesc = Object.getOwnPropertyDescriptor(filesystem, "capabilities");
|
|
3039
|
-
return !!capDesc && "value" in capDesc && capDesc.value === owner.capabilities;
|
|
3076
|
+
return (capDesc === undefined && Object.getOwnPropertyDescriptor(MemoryFileSystem.prototype, "capabilities")?.get === memoryImplementation.capabilities?.get) || (!!capDesc && "value" in capDesc && capDesc.value === owner.capabilities);
|
|
3040
3077
|
}
|
|
3041
3078
|
function stockRetainedResize(filesystem) {
|
|
3042
3079
|
if (!stockDescriptorWrite(filesystem) || !isStockMemoryMethods(filesystem, stockRetainedResizeMethodNames, false))
|
|
@@ -3395,7 +3432,7 @@ export function tryResolveMemoryDevicePath(filesystem, path, resizeCreate) {
|
|
|
3395
3432
|
start = slash + 1;
|
|
3396
3433
|
}
|
|
3397
3434
|
}
|
|
3398
|
-
const cachedMethodValuesMap = new WeakMap();
|
|
3435
|
+
const cachedMethodValuesMap = /* @__PURE__ */ new WeakMap();
|
|
3399
3436
|
function getExpectedMethodValues(names) {
|
|
3400
3437
|
let vals = cachedMethodValuesMap.get(names);
|
|
3401
3438
|
if (!vals) {
|
|
@@ -3718,3 +3755,17 @@ export function tryWriteMemoryFileInDirSync(filesystem, dirPrefix, name, data, a
|
|
|
3718
3755
|
mem.writeMemoryFileInDirFast(dirPrefix, name, data, append, mode);
|
|
3719
3756
|
return true;
|
|
3720
3757
|
}
|
|
3758
|
+
const _memHooks = (globalThis.__safeBashMemHooks ??= { protos: new Set(), byProto: new Map() });
|
|
3759
|
+
_memHooks.protos.add(MemoryFileSystem.prototype);
|
|
3760
|
+
_memHooks.byProto.set(MemoryFileSystem.prototype, {
|
|
3761
|
+
tryGetMemoryDirectoryEntryNamesSync,
|
|
3762
|
+
tryReadMemoryFileViewSync,
|
|
3763
|
+
tryResolveMemoryDevicePath,
|
|
3764
|
+
tryOpenMemoryRedirectHandleSync,
|
|
3765
|
+
tryWriteMemoryFileSync,
|
|
3766
|
+
tryMkdirMemorySync,
|
|
3767
|
+
tryRmRfMemorySync,
|
|
3768
|
+
tryWriteMemoryFileInDirSync,
|
|
3769
|
+
});
|
|
3770
|
+
_memHooks.isCleanAbsolutePath = isCleanAbsolutePath;
|
|
3771
|
+
_memHooks.bindConditionalMutation = bindConditionalMutation;
|
|
@@ -23,8 +23,4 @@ export declare class MemoryAllocation {
|
|
|
23
23
|
constructor(data: Uint8Array, ledger: MemoryLedger);
|
|
24
24
|
retain(): void;
|
|
25
25
|
release(): void;
|
|
26
|
-
isReleased64(): boolean;
|
|
27
|
-
isReleased65536(): boolean;
|
|
28
|
-
detachLedger(dummyLedger: MemoryLedger): void;
|
|
29
|
-
reuse(ledger?: MemoryLedger): void;
|
|
30
26
|
}
|
|
@@ -89,21 +89,6 @@ export class MemoryAllocation {
|
|
|
89
89
|
this.ledger.release(this.data.byteLength, 0);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
isReleased64() {
|
|
93
|
-
return this.references === 0 && this.data.byteLength === 64;
|
|
94
|
-
}
|
|
95
|
-
isReleased65536() {
|
|
96
|
-
return this.references === 0 && this.data.byteLength === 65536;
|
|
97
|
-
}
|
|
98
|
-
detachLedger(dummyLedger) {
|
|
99
|
-
this.ledger = dummyLedger;
|
|
100
|
-
}
|
|
101
|
-
reuse(ledger) {
|
|
102
|
-
this.references = 1;
|
|
103
|
-
if (ledger !== undefined)
|
|
104
|
-
this.ledger = ledger;
|
|
105
|
-
this.data.fill(0);
|
|
106
|
-
}
|
|
107
92
|
}
|
|
108
93
|
Object.assign(MemoryAllocation.prototype, {
|
|
109
94
|
references: 1,
|
|
@@ -7,6 +7,9 @@ export const defaultMemoryFileSystemLimits = Object.freeze({
|
|
|
7
7
|
maxMetadataUnits: Infinity,
|
|
8
8
|
});
|
|
9
9
|
export function normalizeMemoryFileSystemLimits(options) {
|
|
10
|
+
if (options === undefined || (typeof options === "object" && options !== null && Object.getPrototypeOf(options) === Object.prototype && Reflect.ownKeys(options).length === 0)) {
|
|
11
|
+
return defaultMemoryFileSystemLimits;
|
|
12
|
+
}
|
|
10
13
|
const keys = ["maxPathBytes", "maxPathComponents", "maxFileBytes", "maxRetainedBytes", "maxMetadataUnits"];
|
|
11
14
|
const record = readConfigRecord(options, "memory option", [...keys, "maxBytes"]);
|
|
12
15
|
const limits = { ...defaultMemoryFileSystemLimits };
|
|
@@ -29,7 +29,7 @@ export async function resolveEntryView(filesystem, path, options = {}) {
|
|
|
29
29
|
readOnly ||= location.readOnly === true || location.filesystem.capabilities.readOnly === true;
|
|
30
30
|
if (location.stat)
|
|
31
31
|
return { filesystem: location.filesystem, path: location.path, stat: location.stat, readOnly };
|
|
32
|
-
const resolve = resolvers.get(location.filesystem);
|
|
32
|
+
const resolve = resolvers.get(location.filesystem) ?? location.filesystem._resolveEntryView;
|
|
33
33
|
if (!resolve) {
|
|
34
34
|
let stat;
|
|
35
35
|
let followedPath;
|
|
@@ -49,7 +49,7 @@ export async function resolveEntryView(filesystem, path, options = {}) {
|
|
|
49
49
|
}
|
|
50
50
|
let next;
|
|
51
51
|
try {
|
|
52
|
-
next = await resolve(location.path, options);
|
|
52
|
+
next = await resolve.call(location.filesystem, location.path, options);
|
|
53
53
|
}
|
|
54
54
|
catch (error) {
|
|
55
55
|
options.signal?.throwIfAborted();
|
|
@@ -74,7 +74,7 @@ export async function compareResolvedEntries(own, peer, options = {}) {
|
|
|
74
74
|
let result = "unknown";
|
|
75
75
|
for (const [left, right] of [[own, peer], [peer, own]]) {
|
|
76
76
|
options.signal?.throwIfAborted();
|
|
77
|
-
const authority = authorities.get(left.filesystem);
|
|
77
|
+
const authority = authorities.get(left.filesystem) ?? left.filesystem._entryAuthority;
|
|
78
78
|
const key = authority ?? left.filesystem;
|
|
79
79
|
if (queried.has(key))
|
|
80
80
|
continue;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { FileStat } from "../../contracts/filesystem.js";
|
|
2
2
|
export declare function compareIdentity(left: FileStat | undefined, right: FileStat | undefined): "same" | "distinct" | "unknown";
|
|
3
|
+
/** Compare content snapshots, including adapter generations when supplied.
|
|
4
|
+
* Identity is checked separately so identityless streaming adapters remain usable.
|
|
5
|
+
*/
|
|
6
|
+
export declare function compareFileVersion(left: FileStat, right: FileStat): boolean;
|
|
@@ -19,3 +19,11 @@ export function compareIdentity(left, right) {
|
|
|
19
19
|
return "distinct";
|
|
20
20
|
return "unknown";
|
|
21
21
|
}
|
|
22
|
+
/** Compare content snapshots, including adapter generations when supplied.
|
|
23
|
+
* Identity is checked separately so identityless streaming adapters remain usable.
|
|
24
|
+
*/
|
|
25
|
+
export function compareFileVersion(left, right) {
|
|
26
|
+
return left.type === right.type && left.size === right.size
|
|
27
|
+
&& left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs
|
|
28
|
+
&& left.revision === right.revision && left.opaqueVersion === right.opaqueVersion;
|
|
29
|
+
}
|
|
@@ -12,6 +12,7 @@ export declare class MountFileSystem implements FileSystem {
|
|
|
12
12
|
private namespaceMutation;
|
|
13
13
|
private readonly namespaceWaiters;
|
|
14
14
|
constructor(options: MountFileSystemOptions);
|
|
15
|
+
confineExtraction(roots: readonly string[], options?: FsOptions): Promise<FileSystem>;
|
|
15
16
|
private select;
|
|
16
17
|
capabilitiesFor(path: string, options?: CapabilityQueryOptions): Promise<FileSystemCapabilities>;
|
|
17
18
|
private supportsStagingAncestry;
|
|
@@ -9,6 +9,7 @@ import { pathNamespace } from "../path-namespace.js";
|
|
|
9
9
|
import { createStagingCleanup, snapshotStagingCreation } from "../staging-cleanup.js";
|
|
10
10
|
import { directoryAncestryPaths, inspectStagingBindings, runStagingGuard, snapshotDirectoryAncestry, snapshotStagingResolution } from "../staging-ancestry.js";
|
|
11
11
|
import { compareIdentity } from "./identity.js";
|
|
12
|
+
import { createReadOnlyFileSystem } from "../readonly/index.js";
|
|
12
13
|
import { snapshotConditionalChmod } from "../conditional-chmod.js";
|
|
13
14
|
import { compareEntries, registerEntryAuthority, registerEntryView } from "./comparison.js";
|
|
14
15
|
const syntheticStat = Object.freeze({
|
|
@@ -150,6 +151,36 @@ export class MountFileSystem {
|
|
|
150
151
|
...(retainedRead === undefined ? {} : { retainedRead }),
|
|
151
152
|
});
|
|
152
153
|
}
|
|
154
|
+
async confineExtraction(roots, options = {}) {
|
|
155
|
+
options.signal?.throwIfAborted();
|
|
156
|
+
const root = this.select("/").backend;
|
|
157
|
+
const captured = [...roots];
|
|
158
|
+
for (const path of captured) {
|
|
159
|
+
validatePath(path);
|
|
160
|
+
if (normalizePath(globalPath(path)) !== path)
|
|
161
|
+
fail("EINVAL");
|
|
162
|
+
// A backing confinement retains its own root-to-destination ancestry.
|
|
163
|
+
// It cannot retain directories belonging to another mounted backend.
|
|
164
|
+
if (this.select(path).path !== "/")
|
|
165
|
+
fail("ENOTSUP");
|
|
166
|
+
}
|
|
167
|
+
if (!root.confineExtraction)
|
|
168
|
+
fail("ENOTSUP");
|
|
169
|
+
const confined = await root.confineExtraction(captured, options);
|
|
170
|
+
options.signal?.throwIfAborted();
|
|
171
|
+
const observations = new Set(["prepareDirectoryAncestry", "prepareStagingResolution"]);
|
|
172
|
+
const backing = new Proxy(confined, {
|
|
173
|
+
get(target, key) {
|
|
174
|
+
// These only capture/validate bindings. Mutations must always use the
|
|
175
|
+
// backend's confined view, including creation, metadata and hardlinks.
|
|
176
|
+
const owner = observations.has(key) ? root : target;
|
|
177
|
+
const value = Reflect.get(owner, key);
|
|
178
|
+
return typeof value === "function" ? value.bind(owner) : value;
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
return new MountFileSystem({ root: backing, mounts: Object.fromEntries(this.mounts.filter(mount => mount.path !== "/").map(mount => [mount.path,
|
|
182
|
+
mount.backend.capabilities.readOnly === true ? mount.backend : createReadOnlyFileSystem(mount.backend)])) });
|
|
183
|
+
}
|
|
153
184
|
select(path) {
|
|
154
185
|
return this.mounts.find((mount) => within(mount.path, path));
|
|
155
186
|
}
|