@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.
@@ -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 = Object.freeze({ ...this.capabilities, retainedStagingCleanup: false });
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
- return { ...this.metadata(typeModes.directory | mode), type: "directory", entries: new Map() };
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() && cache.allocations.length < 128) {
354
- cache.allocations.push(alloc);
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 if (cache.lastFastDirNode === node) {
364
- cache.lastFastDirPrefix = "";
365
- cache.lastFastDirNode = undefined;
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 = Date.now();
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.createDirectories === undefined && options.resizeCreate === undefined
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
- const node = current.entries.get(name);
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
- const next = current.entries.get(name);
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
- const allocations = memoryCaches.get(this.ledger).allocations;
720
- if (length === 64 && allocations.length > 0) {
721
- const pooled = allocations.pop();
722
- pooled.reuse();
723
- return pooled;
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 capacity = Math.min(Math.max(length, curLen * 2, 64), this.ledger.limits.maxFileBytes, this.ledger.availableBytes);
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 && (append || (length < 64 && this.ledger.availableBytes > 65536 && this.ledger.limits.maxFileBytes >= 64))
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, cleanup: this.retainStagingCleanup(staging, location.parent, directory, file, location.name, name) });
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
- return createStagingCleanup(staging.directory.path, options => {
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(staging, {
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).clearWrites();
1533
- const removed = [node];
1534
- let keyChars = 0;
1535
- let keyEntries = 0;
1536
- for (let index = 0; index < removed.length; index++) {
1537
- const entry = removed[index];
1538
- if (entry.type === "directory" && entry.entries.size > 0) {
1539
- this.permission(entry, 7, syscall, path);
1540
- entry.entries.forEach((child, name) => {
1541
- removed.push(child);
1542
- keyChars += name.length;
1543
- keyEntries++;
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
- location.parent.entries.delete(location.name);
1548
- this.ledger.release((location.name.length + keyChars) * 2, 1 + keyEntries);
1549
- for (const entry of removed) {
1550
- if (entry.type === "directory") {
1551
- entry.entries.clear();
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
- entry.nlink--;
1554
- entry.ctimeMs = Date.now();
1555
- entry.revision = Math.min(Number.MAX_SAFE_INTEGER + 1, entry.revision + 1);
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
+ }