@effect-vfs/core 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/Caller.d.ts +2 -0
- package/dist/Caller.d.ts.map +1 -1
- package/dist/Caller.js +3 -1
- package/dist/LiveVolume.d.ts +69 -1
- package/dist/LiveVolume.d.ts.map +1 -1
- package/dist/LiveVolume.js +80 -3
- package/dist/Search.d.ts.map +1 -1
- package/dist/Search.js +12 -4
- package/dist/VfsError.d.ts +3 -1
- package/dist/VfsError.d.ts.map +1 -1
- package/dist/VfsError.js +3 -1
- package/dist/VirtualFileSystem.d.ts +25 -1
- package/dist/VirtualFileSystem.d.ts.map +1 -1
- package/dist/VirtualFileSystem.js +10 -4
- package/dist/Watch.d.ts +18 -0
- package/dist/Watch.d.ts.map +1 -1
- package/dist/Watch.js +12 -0
- package/dist/internal/callerWatch.d.ts +32 -0
- package/dist/internal/callerWatch.d.ts.map +1 -0
- package/dist/internal/callerWatch.js +184 -0
- package/dist/internal/confinement.d.ts +16 -0
- package/dist/internal/confinement.d.ts.map +1 -0
- package/dist/internal/confinement.js +79 -0
- package/dist/internal/directoryEntries.d.ts +6 -0
- package/dist/internal/directoryEntries.d.ts.map +1 -0
- package/dist/internal/directoryEntries.js +79 -0
- package/dist/internal/fixture.js +1 -1
- package/dist/internal/path.js +2 -2
- package/dist/internal/resolution.d.ts +8 -4
- package/dist/internal/resolution.d.ts.map +1 -1
- package/dist/internal/resolution.js +68 -13
- package/dist/internal/tokenRegistry.d.ts +6 -0
- package/dist/internal/tokenRegistry.d.ts.map +1 -1
- package/dist/internal/tokenRegistry.js +21 -3
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +245 -80
- package/dist/internal/watchHub.d.ts +6 -2
- package/dist/internal/watchHub.d.ts.map +1 -1
- package/dist/internal/watchHub.js +31 -6
- package/package.json +5 -3
|
@@ -23,8 +23,11 @@ import { Mode, OwnerUpdate, Times } from "../Metadata.js";
|
|
|
23
23
|
import { isEntry, Target } from "../Target.js";
|
|
24
24
|
import { make as makeError } from "../VfsError.js";
|
|
25
25
|
import { OverlayChange, OverlayChangesOptions, ReferenceKey, VolumeId, VolumeIdentity, VolumeIncarnation } from "../Volume.js";
|
|
26
|
-
import { WatchOptions } from "../Watch.js";
|
|
26
|
+
import { CallerWatchOptions, WatchOptions } from "../Watch.js";
|
|
27
27
|
import { sameBytes } from "./bytes.js";
|
|
28
|
+
import * as CallerWatch from "./callerWatch.js";
|
|
29
|
+
import * as Confinement from "./confinement.js";
|
|
30
|
+
import * as DirectoryEntries from "./directoryEntries.js";
|
|
28
31
|
import { argumentFailure, decodeConfiguration, errorPath, fsFailure, imageFailure, OpContext, retargetFailure, VfsError } from "./errors.js";
|
|
29
32
|
import { KeySecret, VolumeEpoch } from "./hex128.js";
|
|
30
33
|
import { hmacSha256, sameTag } from "./hmac.js";
|
|
@@ -112,14 +115,16 @@ class Draft extends Context.Service()("@effect-vfs/core/internal/virtualFileSyst
|
|
|
112
115
|
finished: undefined,
|
|
113
116
|
entries: base.entries,
|
|
114
117
|
usedBytes: base.usedBytes,
|
|
118
|
+
renames: [],
|
|
115
119
|
events: [],
|
|
116
120
|
after: [],
|
|
117
121
|
removed: []
|
|
118
122
|
});
|
|
119
|
-
const
|
|
123
|
+
const lookup = (ino) => {
|
|
120
124
|
const draft = MutableRef.get(cell);
|
|
121
125
|
return draft.pending.has(ino) ? draft.pending.get(ino) : InodeTable.get(base.inodes, ino);
|
|
122
|
-
}
|
|
126
|
+
};
|
|
127
|
+
const get = (ino) => Effect.sync(() => lookup(ino));
|
|
123
128
|
const openCount = (ino) => Effect.sync(() => MutableRef.get(cell).opens?.get(ino) ?? base.open.get(ino) ?? 0);
|
|
124
129
|
const put = (node, changed) => Effect.sync(() => {
|
|
125
130
|
MutableRef.update(cell, (draft) => {
|
|
@@ -130,6 +135,7 @@ class Draft extends Context.Service()("@effect-vfs/core/internal/virtualFileSyst
|
|
|
130
135
|
});
|
|
131
136
|
return {
|
|
132
137
|
revision,
|
|
138
|
+
lookup,
|
|
133
139
|
get,
|
|
134
140
|
put: (node) => put(node, true),
|
|
135
141
|
putQuiet: (node) => put(node, false),
|
|
@@ -167,6 +173,9 @@ class Draft extends Context.Service()("@effect-vfs/core/internal/virtualFileSyst
|
|
|
167
173
|
addUsedBytes: (amount) => Effect.sync(() => {
|
|
168
174
|
MutableRef.update(cell, (draft) => ({ ...draft, usedBytes: draft.usedBytes + amount }));
|
|
169
175
|
}),
|
|
176
|
+
rename: (rename) => Effect.sync(() => {
|
|
177
|
+
MutableRef.get(cell).renames.push(rename);
|
|
178
|
+
}),
|
|
170
179
|
publish: (event) => Effect.sync(() => {
|
|
171
180
|
MutableRef.get(cell).events.push(event);
|
|
172
181
|
}),
|
|
@@ -203,19 +212,15 @@ class Draft extends Context.Service()("@effect-vfs/core/internal/virtualFileSyst
|
|
|
203
212
|
return finished;
|
|
204
213
|
}),
|
|
205
214
|
actions: Effect.sync(() => {
|
|
206
|
-
const { events, after, removed } = MutableRef.get(cell);
|
|
207
|
-
return { events, after, removed };
|
|
215
|
+
const { events, after, removed, renames } = MutableRef.get(cell);
|
|
216
|
+
return { events, after, removed, renames };
|
|
208
217
|
})
|
|
209
218
|
};
|
|
210
219
|
})
|
|
211
220
|
}) {
|
|
212
221
|
}
|
|
213
222
|
const withMetadata = (node) => ({ ...node.metadata, revision: node.revision });
|
|
214
|
-
const withEntries = (directory,
|
|
215
|
-
const entries = new Map(directory.entries);
|
|
216
|
-
edit(entries);
|
|
217
|
-
return { ...directory, entries };
|
|
218
|
-
};
|
|
223
|
+
const withEntries = (directory, entries) => ({ ...directory, entries });
|
|
219
224
|
const withoutLink = (links, parent, name) => {
|
|
220
225
|
const index = links.findIndex((link) => link.parent === parent && link.name === name);
|
|
221
226
|
return index < 0 ? links : [...links.slice(0, index), ...links.slice(index + 1)];
|
|
@@ -320,6 +325,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
320
325
|
if (live !== undefined)
|
|
321
326
|
state = live.value;
|
|
322
327
|
const view = (ino) => Effect.flatMap(Effect.serviceOption(Draft), (draft) => Option.isSome(draft) ? draft.value.get(ino) : Effect.sync(() => getNode(state, ino)));
|
|
328
|
+
// Select the operation's namespace at execution time; never retain it across operations.
|
|
329
|
+
const authorityView = Effect.map(Effect.serviceOption(Draft), (draft) => Option.isSome(draft) ? draft.value.lookup : (ino) => getNode(state, ino));
|
|
323
330
|
const registry = TokenRegistry.make(volumeIdentity, view);
|
|
324
331
|
const referenceFor = registry.referenceFor;
|
|
325
332
|
const makeFileReference = registry.file;
|
|
@@ -337,7 +344,11 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
337
344
|
for (const apply of actions.after)
|
|
338
345
|
apply();
|
|
339
346
|
const after = state;
|
|
340
|
-
watchHub.publishUnsafe(() => actions.events.flatMap((events) => Array.from(events(after))), {
|
|
347
|
+
watchHub.publishUnsafe(() => actions.events.flatMap((events) => Array.from(events(after))), {
|
|
348
|
+
before,
|
|
349
|
+
after,
|
|
350
|
+
renames: actions.renames
|
|
351
|
+
});
|
|
341
352
|
});
|
|
342
353
|
const transition = Effect.fnUntraced(function* (change) {
|
|
343
354
|
const current = yield* Draft.make(state);
|
|
@@ -420,10 +431,14 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
420
431
|
return SLASH_HEX + names.reverse().join(SLASH_HEX);
|
|
421
432
|
});
|
|
422
433
|
const entryPath = (prefix, name) => ownedPath(nameBytes(prefix + (prefix === SLASH_HEX ? "" : SLASH_HEX) + name));
|
|
423
|
-
const publishEntry = (_tag, parent, name, ino)
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
434
|
+
const publishEntry = Effect.fnUntraced(function* (_tag, parent, name, ino) {
|
|
435
|
+
const before = state;
|
|
436
|
+
const draft = yield* Draft;
|
|
437
|
+
yield* draft.publish((installed) => {
|
|
438
|
+
const prefix = pathOf((at) => getNode(installed, at), parent) ?? pathOf((at) => getNode(before, at), parent);
|
|
439
|
+
return prefix === undefined ? [] : [{ change: { _tag, path: entryPath(prefix, name) }, parent, ino, name }];
|
|
440
|
+
});
|
|
441
|
+
});
|
|
427
442
|
const publishNode = Effect.fnUntraced(function* (ino) {
|
|
428
443
|
const draft = yield* Draft;
|
|
429
444
|
const target = yield* draft.get(ino);
|
|
@@ -599,7 +614,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
599
614
|
mtimeNs: now,
|
|
600
615
|
ctimeNs: now
|
|
601
616
|
}
|
|
602
|
-
}, (entries
|
|
617
|
+
}, DirectoryEntries.set(parent.entries, name, child.ino)));
|
|
603
618
|
if (child.kind === "directory")
|
|
604
619
|
yield* d.put({ ...child, parent: parent.ino, name });
|
|
605
620
|
else {
|
|
@@ -667,12 +682,14 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
667
682
|
yield* publishNode(file.ino);
|
|
668
683
|
});
|
|
669
684
|
const fileHandle = (ref) => {
|
|
670
|
-
const get = Effect.fnUntraced(function* (op, access) {
|
|
685
|
+
const get = Effect.fnUntraced(function* (op, access, cleanup = false) {
|
|
671
686
|
const ino = TokenRegistry.inode(ref);
|
|
672
687
|
const node = ino === undefined ? undefined : yield* view(ino);
|
|
673
688
|
if (node?.kind !== "file" || (access === "read" && ref.access === "write") ||
|
|
674
689
|
(access === "write" && ref.access === "read"))
|
|
675
690
|
return yield* op.fail("InvalidHandle");
|
|
691
|
+
if (!cleanup && ref.authority !== undefined)
|
|
692
|
+
yield* ref.authority(node, op);
|
|
676
693
|
return node;
|
|
677
694
|
});
|
|
678
695
|
const read = (maximum, position) => {
|
|
@@ -754,19 +771,19 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
754
771
|
const closeOp = OpContext.make("close");
|
|
755
772
|
const handle = Object.freeze({
|
|
756
773
|
[FileHandleId]: true,
|
|
757
|
-
read: Effect.
|
|
774
|
+
read: Effect.fnUntraced(function* (maximum) {
|
|
758
775
|
return (yield* read(maximum)).bytes;
|
|
759
776
|
}),
|
|
760
|
-
pread: Effect.
|
|
777
|
+
pread: Effect.fnUntraced(function* (maximum, offset) {
|
|
761
778
|
return yield* read(maximum, offset);
|
|
762
779
|
}),
|
|
763
|
-
write: Effect.
|
|
780
|
+
write: Effect.fnUntraced(function* (bytes) {
|
|
764
781
|
return yield* write(bytes);
|
|
765
782
|
}),
|
|
766
|
-
pwrite: Effect.
|
|
783
|
+
pwrite: Effect.fnUntraced(function* (bytes, offset) {
|
|
767
784
|
return yield* write(bytes, offset);
|
|
768
785
|
}),
|
|
769
|
-
seek: Effect.
|
|
786
|
+
seek: Effect.fnUntraced(function* (offset, mode) {
|
|
770
787
|
const op = OpContext.make("seek");
|
|
771
788
|
return yield* coordinatedRead(op, Effect.uninterruptible(Effect.gen(function* () {
|
|
772
789
|
const file = yield* get(op);
|
|
@@ -791,23 +808,55 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
791
808
|
return next;
|
|
792
809
|
})));
|
|
793
810
|
}),
|
|
794
|
-
truncate: Effect.
|
|
811
|
+
truncate: Effect.fnUntraced(function* (length) {
|
|
795
812
|
const op = OpContext.make("truncate");
|
|
796
813
|
return yield* coordinated(op, Effect.gen(function* () {
|
|
797
814
|
yield* resize(yield* get(op, "write"), length, op);
|
|
798
815
|
}));
|
|
799
816
|
}),
|
|
800
|
-
stat: coordinatedRead(statOp, Effect.map(Effect.suspend(() => get(statOp)), withMetadata))
|
|
801
|
-
sync: coordinatedRead(syncOp, Effect.suspend(() => Effect.asVoid(get(syncOp))))
|
|
802
|
-
close: closeFile(ref, closeOp, Effect.suspend(() => get(closeOp))).pipe(Effect.ensuring(closeReleasedScope(ref))
|
|
817
|
+
stat: coordinatedRead(statOp, Effect.map(Effect.suspend(() => get(statOp)), withMetadata)),
|
|
818
|
+
sync: coordinatedRead(syncOp, Effect.suspend(() => Effect.asVoid(get(syncOp)))),
|
|
819
|
+
close: closeFile(ref, closeOp, Effect.suspend(() => get(closeOp, undefined, true))).pipe(Effect.ensuring(closeReleasedScope(ref)))
|
|
803
820
|
});
|
|
804
821
|
registry.registerFile(handle, ref);
|
|
805
822
|
return handle;
|
|
806
823
|
};
|
|
807
|
-
const createCaller = (reference, identity, umask) => {
|
|
824
|
+
const createCaller = (reference, identity, umask, roots = [], inherited = []) => {
|
|
825
|
+
const boundary = roots.length === 0 ? undefined : Confinement.make(authorityView, roots);
|
|
826
|
+
inherited = inherited.filter((chain, index) => !((chain.length <= roots.length && chain.every((ino, at) => roots[at] === ino)) ||
|
|
827
|
+
inherited.slice(0, index).some((other) => other.length === chain.length && chain.every((ino, at) => other[at] === ino))));
|
|
828
|
+
const additional = inherited.map((chain) => Confinement.make(authorityView, chain));
|
|
829
|
+
const checkAuthority = Effect.fnUntraced(function* (node, op) {
|
|
830
|
+
if (boundary !== undefined)
|
|
831
|
+
yield* boundary.check(node, op);
|
|
832
|
+
for (const authority of additional)
|
|
833
|
+
yield* authority.check(node, op);
|
|
834
|
+
});
|
|
835
|
+
const inheritedFor = (target) => Target.$is("Handle")(target)
|
|
836
|
+
? registry.handleBoundaries(target.handle)
|
|
837
|
+
: Target.$is("Path")(target) && target.relativeTo !== undefined
|
|
838
|
+
? registry.handleBoundaries(target.relativeTo)
|
|
839
|
+
: [];
|
|
840
|
+
const boundariesFor = (target) => [...(roots.length > 0 ? [roots] : []), ...inherited, ...(target === undefined ? [] : inheritedFor(target))];
|
|
841
|
+
const assignAuthority = (ref, target) => {
|
|
842
|
+
const chains = boundariesFor(target).filter((chain, index, all) => !all.some((other, at) => at !== index && other.length >= chain.length && chain.every((ino, offset) => other[offset] === ino) &&
|
|
843
|
+
(other.length > chain.length || at < index)));
|
|
844
|
+
if (chains.length === 0)
|
|
845
|
+
return;
|
|
846
|
+
ref.boundaries = chains;
|
|
847
|
+
const authorities = chains.map((chain) => Confinement.make(authorityView, chain));
|
|
848
|
+
ref.authority = Effect.fnUntraced(function* (node, op) {
|
|
849
|
+
for (const authority of authorities)
|
|
850
|
+
yield* authority.check(node, op, true);
|
|
851
|
+
});
|
|
852
|
+
};
|
|
853
|
+
const callerPath = boundary?.path ?? pathOfCurrent;
|
|
808
854
|
const resolver = Resolution.make({
|
|
809
855
|
caller: reference,
|
|
856
|
+
confinement: boundary,
|
|
857
|
+
additional: additional.length === 0 ? undefined : additional,
|
|
810
858
|
get: view,
|
|
859
|
+
authorityView,
|
|
811
860
|
identity,
|
|
812
861
|
maxPathBytes: limits.maxPathBytes,
|
|
813
862
|
registry
|
|
@@ -965,6 +1014,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
965
1014
|
: Effect.void;
|
|
966
1015
|
const claimName = Effect.fnUntraced(function* (entry, trailingSlash = "allowed") {
|
|
967
1016
|
const parent = yield* directoryNow(entry.parent);
|
|
1017
|
+
if (boundary !== undefined && parent.ino === boundary.root &&
|
|
1018
|
+
(entry.name === undefined || isDotComponent(entry.name)))
|
|
1019
|
+
return yield* entry.op.fail("NotPermitted");
|
|
968
1020
|
if (isDotComponent(entry.name)) {
|
|
969
1021
|
return yield* entry.op.fail(entry.addressing === "entry" ? "InvalidArgument" : "AlreadyExists");
|
|
970
1022
|
}
|
|
@@ -1071,7 +1123,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1071
1123
|
mtimeNs: now,
|
|
1072
1124
|
ctimeNs: now
|
|
1073
1125
|
}
|
|
1074
|
-
}, (entries
|
|
1126
|
+
}, DirectoryEntries.remove(parent.entries, name)));
|
|
1075
1127
|
yield* publishEntry("Remove", parent.ino, name, child.ino);
|
|
1076
1128
|
yield* detach(child, parent.ino, name, now);
|
|
1077
1129
|
yield* d.addEntries(-1);
|
|
@@ -1080,8 +1132,13 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1080
1132
|
// Authorizes removing from the entry's directory and returns the named child. Only a path can name a dot
|
|
1081
1133
|
// entry, and each verb reports it with its own code. Search permission comes before the lookup and write
|
|
1082
1134
|
// permission after it, as on Linux; `beforeWrite` holds the verb's own checks that Linux makes in between.
|
|
1135
|
+
const protectsRoot = (parent, name) => boundary !== undefined &&
|
|
1136
|
+
((parent.ino === boundary.root && (name === undefined || isDotComponent(name))) ||
|
|
1137
|
+
(name === "2e2e" && parent.parent === boundary.root));
|
|
1083
1138
|
const removalTarget = Effect.fnUntraced(function* (entry, dotNameCode, beforeWrite) {
|
|
1084
1139
|
const parent = yield* directoryNow(entry.parent);
|
|
1140
|
+
if (protectsRoot(parent, entry.name))
|
|
1141
|
+
return yield* entry.op.fail("NotPermitted");
|
|
1085
1142
|
if (isDotComponent(entry.name)) {
|
|
1086
1143
|
return yield* entry.op.fail(entry.addressing === "entry" ? "InvalidArgument" : dotNameCode);
|
|
1087
1144
|
}
|
|
@@ -1090,6 +1147,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1090
1147
|
const child = childIno === undefined ? undefined : (yield* view(childIno));
|
|
1091
1148
|
if (child === undefined)
|
|
1092
1149
|
return yield* entry.op.fail("NotFound");
|
|
1150
|
+
if (child.ino === boundary?.root)
|
|
1151
|
+
return yield* entry.op.fail("NotPermitted");
|
|
1093
1152
|
const rejected = beforeWrite?.(child);
|
|
1094
1153
|
if (rejected !== undefined)
|
|
1095
1154
|
return yield* rejected;
|
|
@@ -1127,6 +1186,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1127
1186
|
const sameDirectory = source.parent === destination.parent;
|
|
1128
1187
|
const sourceName = source.name;
|
|
1129
1188
|
const destinationName = destination.name;
|
|
1189
|
+
if (protectsRoot(sourceDirectory, sourceName) || protectsRoot(destinationDirectory, destinationName))
|
|
1190
|
+
return yield* source.op.fail("NotPermitted");
|
|
1130
1191
|
if (isDotComponent(sourceName) || isDotComponent(destinationName)) {
|
|
1131
1192
|
return yield* source.op.fail("InvalidArgument");
|
|
1132
1193
|
}
|
|
@@ -1142,6 +1203,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1142
1203
|
return yield* source.op.fail("NotFound");
|
|
1143
1204
|
const replacedIno = destinationDirectory.entries.get(destinationName);
|
|
1144
1205
|
const replaced = replacedIno === undefined ? undefined : (yield* view(replacedIno));
|
|
1206
|
+
if (boundary !== undefined && (child.ino === boundary.root || replaced?.ino === boundary.root)) {
|
|
1207
|
+
return yield* source.op.fail("NotPermitted");
|
|
1208
|
+
}
|
|
1145
1209
|
// A trailing slash on either side asks for a directory; a missing slashed destination is fine when the
|
|
1146
1210
|
// source is one, as Linux allows.
|
|
1147
1211
|
if ((source.trailingSlash || destination.trailingSlash) && child.kind !== "directory") {
|
|
@@ -1190,6 +1254,11 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1190
1254
|
}
|
|
1191
1255
|
const now = yield* timestamp(op);
|
|
1192
1256
|
const d = yield* Draft;
|
|
1257
|
+
yield* d.rename({
|
|
1258
|
+
ino: child.ino,
|
|
1259
|
+
from: { parent: source.parent, name: sourceName },
|
|
1260
|
+
to: { parent: destination.parent, name: destinationName }
|
|
1261
|
+
});
|
|
1193
1262
|
yield* publishEntry("Remove", sourceDirectory.ino, sourceName, child.ino);
|
|
1194
1263
|
yield* d.put(withEntries({
|
|
1195
1264
|
...sourceDirectory,
|
|
@@ -1199,7 +1268,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1199
1268
|
mtimeNs: now,
|
|
1200
1269
|
ctimeNs: now
|
|
1201
1270
|
}
|
|
1202
|
-
}, (entries
|
|
1271
|
+
}, DirectoryEntries.remove(sourceDirectory.entries, sourceName)));
|
|
1203
1272
|
const destinationNow = yield* directoryNow(destination.parent);
|
|
1204
1273
|
yield* d.put(withEntries({
|
|
1205
1274
|
...destinationNow,
|
|
@@ -1209,7 +1278,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1209
1278
|
mtimeNs: now,
|
|
1210
1279
|
ctimeNs: now
|
|
1211
1280
|
}
|
|
1212
|
-
}, (entries
|
|
1281
|
+
}, DirectoryEntries.set(destinationNow.entries, destinationName, child.ino)));
|
|
1213
1282
|
const moved = yield* nodeNow(child.ino);
|
|
1214
1283
|
if (moved.kind === "directory") {
|
|
1215
1284
|
yield* d.put({
|
|
@@ -1309,6 +1378,21 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1309
1378
|
return yield* acquireHandle(acquired, (acquire) => coordinatedRead(op, Effect.uninterruptible(acquire)), Effect.gen(function* () {
|
|
1310
1379
|
const directory = yield* resolveDirectory(target, op);
|
|
1311
1380
|
yield* authorize(directory.node, identity, EXECUTE, directory.op);
|
|
1381
|
+
assignAuthority(acquired, target);
|
|
1382
|
+
const candidates = acquired.boundaries ?? [];
|
|
1383
|
+
if (candidates.length === 1)
|
|
1384
|
+
acquired.namespaceRoots = candidates[0];
|
|
1385
|
+
let current = directory.node;
|
|
1386
|
+
while (candidates.length > 1) {
|
|
1387
|
+
const nearest = candidates.find((chain) => chain.at(-1) === current.ino);
|
|
1388
|
+
if (nearest !== undefined) {
|
|
1389
|
+
acquired.namespaceRoots = nearest;
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
if (current.ino === ROOT_INO)
|
|
1393
|
+
break;
|
|
1394
|
+
current = yield* directoryNow(current.parent);
|
|
1395
|
+
}
|
|
1312
1396
|
holdDirectory(directory.node.ino);
|
|
1313
1397
|
TokenRegistry.publish(acquired, directory.node.ino);
|
|
1314
1398
|
return acquired;
|
|
@@ -1345,6 +1429,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1345
1429
|
const file = resolved.kind === "existing" ? resolved.node : undefined;
|
|
1346
1430
|
const result = yield* openFile({ parent: parent.ino, name, trailingSlash: path.trailingSlash, addressing: "path", op: pathOp }, file, chosen, acquired, op);
|
|
1347
1431
|
opened(result.ino);
|
|
1432
|
+
assignAuthority(acquired, target);
|
|
1348
1433
|
return fileHandle(acquired);
|
|
1349
1434
|
}));
|
|
1350
1435
|
});
|
|
@@ -1368,6 +1453,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1368
1453
|
yield* draft.retain(node.ino);
|
|
1369
1454
|
yield* bindFile(acquired, node.ino);
|
|
1370
1455
|
opened(node.ino);
|
|
1456
|
+
assignAuthority(acquired, target);
|
|
1371
1457
|
return fileHandle(acquired);
|
|
1372
1458
|
}));
|
|
1373
1459
|
});
|
|
@@ -1425,7 +1511,10 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1425
1511
|
const result = yield* openFile({ parent: mutationParent, name: mutationName, trailingSlash: false, addressing: "entry", op }, file, { ...chosen, initialSize: chosen.initialSize ?? 0n }, acquired, op);
|
|
1426
1512
|
opened(result.ino);
|
|
1427
1513
|
return {
|
|
1428
|
-
handle:
|
|
1514
|
+
handle: (() => {
|
|
1515
|
+
assignAuthority(acquired, entry.directory);
|
|
1516
|
+
return fileHandle(acquired);
|
|
1517
|
+
})(),
|
|
1429
1518
|
reference: referenceFor(result.ino),
|
|
1430
1519
|
created: result.created,
|
|
1431
1520
|
directory: result.directory
|
|
@@ -1492,10 +1581,14 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1492
1581
|
// through a reference or a handle is held by the object it resolved to, as a descriptor holds it.
|
|
1493
1582
|
// It steps down the chain in a loop, so a tree of any depth reaches its frames without deepening the stack.
|
|
1494
1583
|
const reachFrame = (frame, at) => Effect.gen(function* () {
|
|
1584
|
+
if (boundary !== undefined)
|
|
1585
|
+
yield* boundary.alive(at);
|
|
1495
1586
|
const chain = [];
|
|
1496
1587
|
for (let link = frame; link !== undefined; link = link.up)
|
|
1497
1588
|
chain.push(link);
|
|
1498
1589
|
let node = yield* view((chain.at(-1) ?? frame).parent);
|
|
1590
|
+
if (node !== undefined)
|
|
1591
|
+
yield* checkAuthority(node, at);
|
|
1499
1592
|
for (let index = chain.length - 1; index >= 0; index--) {
|
|
1500
1593
|
const link = chain[index];
|
|
1501
1594
|
if (link === undefined || node?.kind !== "directory" || node.metadata.nlink === 0)
|
|
@@ -1582,13 +1675,15 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1582
1675
|
// failure, which names the entry's path under the one the caller gave. Removing an empty directory reveals nothing, so only a directory with entries must be readable, and
|
|
1583
1676
|
// no permission is changed to make one so. With `force`, the entry itself going missing leaves nothing to
|
|
1584
1677
|
// empty.
|
|
1585
|
-
const emptyDirectory = Effect.fnUntraced(function* (prepared, op, force) {
|
|
1678
|
+
const emptyDirectory = Effect.fnUntraced(function* (prepared, op, force, guard) {
|
|
1586
1679
|
const prefix = prepared.kind === "path" ? prepared.path.bytes : nameBytes(prepared.name);
|
|
1587
1680
|
const locate = (path) => ownedPath(joinPath(prefix, path));
|
|
1588
|
-
const
|
|
1681
|
+
const targetGuard = guard.pipe(Effect.catchIf((error) => force && error.code === "NotFound", () => Effect.void));
|
|
1682
|
+
const listable = (directory, at) => Effect.andThen(targetGuard, directory.entries.size === 0 ? Effect.void : authorize(directory, identity, READ, at));
|
|
1589
1683
|
let anchor;
|
|
1590
1684
|
let targetAt = preparedOp(prepared, op);
|
|
1591
1685
|
const first = coordinatedRead(op, Effect.gen(function* () {
|
|
1686
|
+
yield* guard;
|
|
1592
1687
|
const entry = yield* resolveEntry(prepared, op);
|
|
1593
1688
|
const parent = yield* directoryNow(entry.parent);
|
|
1594
1689
|
yield* authorize(parent, identity, EXECUTE, entry.op);
|
|
@@ -1607,6 +1702,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1607
1702
|
const removeFrame = (frame) => {
|
|
1608
1703
|
const at = op.at(locate(frame.path));
|
|
1609
1704
|
return Effect.andThen(seams.beforeTreeRemoval, coordinated(op, Effect.gen(function* () {
|
|
1705
|
+
yield* targetGuard;
|
|
1610
1706
|
if (anchor !== undefined && !holds(yield* view(anchor.parent), anchor)) {
|
|
1611
1707
|
return yield* (force ? Effect.void : Effect.fail(targetAt.fail("NotFound")));
|
|
1612
1708
|
}
|
|
@@ -1630,7 +1726,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1630
1726
|
const prepared = yield* Effect.fromResult(prepareEntry(input, op));
|
|
1631
1727
|
return yield* coordinated(op, Effect.flatMap(resolveEntry(prepared, op), (entry) => body(entry, op)));
|
|
1632
1728
|
});
|
|
1633
|
-
const openDispatch = Effect.
|
|
1729
|
+
const openDispatch = Effect.fnUntraced(function* (input, options) {
|
|
1634
1730
|
const op = OpContext.make("open");
|
|
1635
1731
|
if (isEntry(input))
|
|
1636
1732
|
return yield* openEntry(input, options, op);
|
|
@@ -1642,11 +1738,34 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1642
1738
|
function open(input, options) {
|
|
1643
1739
|
return openDispatch(input, options);
|
|
1644
1740
|
}
|
|
1645
|
-
const removeDispatch = Effect.
|
|
1741
|
+
const removeDispatch = Effect.fnUntraced(function* (input, options) {
|
|
1646
1742
|
const op = OpContext.make("remove");
|
|
1647
1743
|
const prepared = yield* Effect.fromResult(prepareEntry(input, op));
|
|
1648
1744
|
const chosen = yield* decodeRemoveOptions(options ?? {}).pipe(Effect.mapError((cause) => fail(preparedOp(prepared, op), cause)));
|
|
1649
|
-
const
|
|
1745
|
+
const imported = prepared.kind === "entry"
|
|
1746
|
+
? inheritedFor(prepared.directory)
|
|
1747
|
+
: prepared.base === undefined
|
|
1748
|
+
? []
|
|
1749
|
+
: registry.handleBoundaries(prepared.base);
|
|
1750
|
+
const guard = chosen.expected === undefined && imported.length === 0 ? Effect.void : Effect.gen(function* () {
|
|
1751
|
+
const entry = yield* resolveEntry(prepared, op);
|
|
1752
|
+
const parent = yield* directoryNow(entry.parent);
|
|
1753
|
+
if (protectsRoot(parent, entry.name))
|
|
1754
|
+
return yield* entry.op.fail("NotPermitted");
|
|
1755
|
+
if (chosen.expected === undefined)
|
|
1756
|
+
return;
|
|
1757
|
+
const expected = yield* registry.referenceIdentity(chosen.expected, op);
|
|
1758
|
+
const expectedNode = yield* view(expected);
|
|
1759
|
+
if (expectedNode !== undefined && expectedNode.metadata.nlink > 0)
|
|
1760
|
+
yield* checkAuthority(expectedNode, op);
|
|
1761
|
+
yield* authorize(parent, identity, EXECUTE, entry.op);
|
|
1762
|
+
const found = entry.name === undefined ? parent.ino : parent.entries.get(entry.name);
|
|
1763
|
+
if (found === undefined)
|
|
1764
|
+
return yield* entry.op.fail("NotFound");
|
|
1765
|
+
if (found !== expected)
|
|
1766
|
+
return yield* entry.op.fail("VolumeBusy", { field: "expected" });
|
|
1767
|
+
});
|
|
1768
|
+
const once = coordinated(op, Effect.andThen(guard, Effect.flatMap(resolveEntry(prepared, op), (entry) => removeEntry(entry, op))));
|
|
1650
1769
|
const target = (effect) => chosen.force === true
|
|
1651
1770
|
? Effect.catchIf(effect, (error) => error.code === "NotFound", () => Effect.undefined)
|
|
1652
1771
|
: effect;
|
|
@@ -1655,7 +1774,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1655
1774
|
return removed.success;
|
|
1656
1775
|
if (chosen.recursive !== true || removed.failure.code !== "NotEmpty")
|
|
1657
1776
|
return yield* removed.failure;
|
|
1658
|
-
yield* emptyDirectory(prepared, op, chosen.force === true);
|
|
1777
|
+
yield* emptyDirectory(prepared, op, chosen.force === true, guard);
|
|
1659
1778
|
return yield* target(once);
|
|
1660
1779
|
});
|
|
1661
1780
|
function remove(input, options) {
|
|
@@ -1663,10 +1782,43 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1663
1782
|
}
|
|
1664
1783
|
const caller = Object.freeze({
|
|
1665
1784
|
[CallerId]: true,
|
|
1785
|
+
limits,
|
|
1786
|
+
watch: Effect.fnUntraced(function* (input, options) {
|
|
1787
|
+
const op = OpContext.make("watch");
|
|
1788
|
+
const decoded = yield* Effect.fromResult(decodeConfiguration(CallerWatchOptions, options ?? {}, "watch"));
|
|
1789
|
+
const target = asTarget(input);
|
|
1790
|
+
const seams = yield* VolumeTestSeams;
|
|
1791
|
+
const select = Effect.gen(function* () {
|
|
1792
|
+
yield* checkAvailable("watch");
|
|
1793
|
+
const resolved = yield* resolveTarget(target, op);
|
|
1794
|
+
yield* authorize(resolved.node, identity, READ, resolved.op);
|
|
1795
|
+
if (resolved.node.metadata.nlink === 0)
|
|
1796
|
+
return yield* resolved.op.fail("NotFound");
|
|
1797
|
+
if (decoded.alias === "resolved" && resolved.kind === "node" && resolved.node.kind !== "directory") {
|
|
1798
|
+
return yield* op.fail("InvalidArgument");
|
|
1799
|
+
}
|
|
1800
|
+
const selection = {
|
|
1801
|
+
roots,
|
|
1802
|
+
additionalRoots: [...inherited, ...inheritedFor(target)],
|
|
1803
|
+
target: resolved.node.ino,
|
|
1804
|
+
recursive: decoded.recursive ?? true
|
|
1805
|
+
};
|
|
1806
|
+
return CallerWatch.make(decoded.alias === "resolved" && resolved.kind === "entry"
|
|
1807
|
+
? { ...selection, selected: { parent: resolved.parent.ino, name: resolved.name } }
|
|
1808
|
+
: selection);
|
|
1809
|
+
});
|
|
1810
|
+
return Stream.map(yield* admit(op, watchHub.subscribe(select, seams.afterSubscribe)), (event) => event.change);
|
|
1811
|
+
}),
|
|
1666
1812
|
root: coordinatedRead(rootOp, Effect.suspend(() => TokenRegistry.inode(reference) === undefined
|
|
1667
1813
|
? Effect.fail(rootOp.fail("ClosedCaller"))
|
|
1668
|
-
: Effect.
|
|
1669
|
-
|
|
1814
|
+
: Effect.gen(function* () {
|
|
1815
|
+
const node = yield* view(boundary?.root ?? ROOT_INO);
|
|
1816
|
+
if (node === undefined)
|
|
1817
|
+
return yield* rootOp.fail("ClosedCaller");
|
|
1818
|
+
yield* checkAuthority(node, rootOp);
|
|
1819
|
+
return referenceFor(node.ino);
|
|
1820
|
+
}))),
|
|
1821
|
+
lookup: Effect.fnUntraced(function* (input) {
|
|
1670
1822
|
const op = OpContext.make("lookup");
|
|
1671
1823
|
const prepared = yield* Effect.fromResult(prepareEntry(input, op));
|
|
1672
1824
|
return yield* coordinatedRead(op, Effect.gen(function* () {
|
|
@@ -1682,21 +1834,24 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1682
1834
|
return referenceFor(child);
|
|
1683
1835
|
}));
|
|
1684
1836
|
}),
|
|
1685
|
-
parent: Effect.
|
|
1837
|
+
parent: Effect.fnUntraced(function* (input) {
|
|
1686
1838
|
const op = OpContext.make("parent");
|
|
1687
1839
|
const target = asTarget(input);
|
|
1688
1840
|
return yield* coordinatedRead(op, Effect.gen(function* () {
|
|
1689
1841
|
const directory = yield* resolveDirectory(target, op);
|
|
1690
1842
|
yield* authorize(directory.node, identity, EXECUTE, directory.op);
|
|
1691
|
-
return referenceFor(directory.node.
|
|
1843
|
+
return referenceFor(directory.node.ino === boundary?.root ||
|
|
1844
|
+
inheritedFor(target).some((chain) => chain.at(-1) === directory.node.ino)
|
|
1845
|
+
? directory.node.ino
|
|
1846
|
+
: directory.node.parent);
|
|
1692
1847
|
}));
|
|
1693
1848
|
}),
|
|
1694
|
-
stat: Effect.
|
|
1849
|
+
stat: Effect.fnUntraced(function* (input) {
|
|
1695
1850
|
const op = OpContext.make("stat");
|
|
1696
1851
|
const target = asTarget(input);
|
|
1697
1852
|
return yield* coordinatedRead(op, Effect.map(resolveTarget(target, op), (resolved) => withMetadata(resolved.node)));
|
|
1698
1853
|
}),
|
|
1699
|
-
readDirectory: Effect.
|
|
1854
|
+
readDirectory: Effect.fnUntraced(function* (input) {
|
|
1700
1855
|
const op = OpContext.make("readDirectory");
|
|
1701
1856
|
const target = asTarget(input);
|
|
1702
1857
|
return yield* accessing(op, Effect.gen(function* () {
|
|
@@ -1712,7 +1867,12 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1712
1867
|
const target = asTarget(input);
|
|
1713
1868
|
const prefix = Target.$is("Path")(target) ? Result.getOrUndefined(inputBytes(target.path)) : undefined;
|
|
1714
1869
|
const locate = (path) => ownedPath(prefix === undefined ? path : joinPath(prefix, path));
|
|
1715
|
-
const
|
|
1870
|
+
const authorities = inheritedFor(target).map((chain) => Confinement.make(authorityView, chain));
|
|
1871
|
+
const readable = Effect.fnUntraced(function* (directory, at) {
|
|
1872
|
+
for (const authority of authorities)
|
|
1873
|
+
yield* authority.check(directory, at, true);
|
|
1874
|
+
yield* authorize(directory, identity, READ, at);
|
|
1875
|
+
});
|
|
1716
1876
|
return Stream.unwrap(Effect.gen(function* () {
|
|
1717
1877
|
const chosen = yield* decodeWalkOptions(options ?? {}).pipe(Effect.mapError((cause) => fail(Target.$is("Path")(target) ? op.at(target.path) : op, cause)));
|
|
1718
1878
|
const first = coordinatedRead(op, Effect.gen(function* () {
|
|
@@ -1743,9 +1903,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1743
1903
|
kind: frame.kind,
|
|
1744
1904
|
depth: frame.depth
|
|
1745
1905
|
}));
|
|
1746
|
-
}))
|
|
1906
|
+
}));
|
|
1747
1907
|
},
|
|
1748
|
-
readLink: Effect.
|
|
1908
|
+
readLink: Effect.fnUntraced(function* (input) {
|
|
1749
1909
|
const op = OpContext.make("readLink");
|
|
1750
1910
|
const target = asTarget(input);
|
|
1751
1911
|
return yield* coordinatedRead(op, Effect.gen(function* () {
|
|
@@ -1755,7 +1915,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1755
1915
|
return new Uint8Array(resolved.node.target);
|
|
1756
1916
|
}));
|
|
1757
1917
|
}),
|
|
1758
|
-
realPath: Effect.
|
|
1918
|
+
realPath: Effect.fnUntraced(function* (input) {
|
|
1759
1919
|
const op = OpContext.make("realPath");
|
|
1760
1920
|
const target = asTarget(input);
|
|
1761
1921
|
return yield* coordinatedRead(op, Effect.gen(function* () {
|
|
@@ -1767,7 +1927,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1767
1927
|
: result.kind === "entry"
|
|
1768
1928
|
? result.parent
|
|
1769
1929
|
: undefined;
|
|
1770
|
-
const prefix = directory === undefined ? SLASH_HEX : yield*
|
|
1930
|
+
const prefix = directory === undefined ? SLASH_HEX : yield* callerPath(directory.ino);
|
|
1771
1931
|
if (prefix === undefined)
|
|
1772
1932
|
return yield* pathOp.fail("NotFound");
|
|
1773
1933
|
if (result.node.kind === "directory" || result.kind === "node")
|
|
@@ -1775,13 +1935,13 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1775
1935
|
return ownedPath(nameBytes(prefix + (prefix === SLASH_HEX ? "" : SLASH_HEX) + result.name));
|
|
1776
1936
|
}
|
|
1777
1937
|
const resolved = yield* resolveTarget(target, op);
|
|
1778
|
-
const path = yield*
|
|
1938
|
+
const path = yield* callerPath(resolved.node.ino);
|
|
1779
1939
|
if (path === undefined)
|
|
1780
1940
|
return yield* resolved.op.fail("NotFound");
|
|
1781
1941
|
return ownedPath(nameBytes(path));
|
|
1782
1942
|
}));
|
|
1783
1943
|
}),
|
|
1784
|
-
access: Effect.
|
|
1944
|
+
access: Effect.fnUntraced(function* (input, bits = 0) {
|
|
1785
1945
|
const op = OpContext.make("access");
|
|
1786
1946
|
const target = asTarget(input);
|
|
1787
1947
|
if (!Number.isInteger(bits) || bits < 0 || bits > (READ | WRITE | EXECUTE)) {
|
|
@@ -1801,7 +1961,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1801
1961
|
return granted;
|
|
1802
1962
|
}));
|
|
1803
1963
|
}),
|
|
1804
|
-
readFile: Effect.
|
|
1964
|
+
readFile: Effect.fnUntraced(function* (input) {
|
|
1805
1965
|
const op = OpContext.make("readFile");
|
|
1806
1966
|
const target = asTarget(input);
|
|
1807
1967
|
return yield* accessing(op, Effect.gen(function* () {
|
|
@@ -1816,7 +1976,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1816
1976
|
return { value: data, access: { node, now: yield* timestamp(op) } };
|
|
1817
1977
|
}));
|
|
1818
1978
|
}),
|
|
1819
|
-
writeFile: Effect.
|
|
1979
|
+
writeFile: Effect.fnUntraced(function* (input, bytes, options) {
|
|
1820
1980
|
const op = OpContext.make("writeFile");
|
|
1821
1981
|
const prepared = yield* Effect.fromResult(prepareEntry(input, op));
|
|
1822
1982
|
const optionsOp = preparedOp(prepared, op);
|
|
@@ -1903,7 +2063,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1903
2063
|
}));
|
|
1904
2064
|
}),
|
|
1905
2065
|
open,
|
|
1906
|
-
mkdir: Effect.
|
|
2066
|
+
mkdir: Effect.fnUntraced(function* (input, options = {}) {
|
|
1907
2067
|
const op = OpContext.make("mkdir");
|
|
1908
2068
|
const prepared = yield* Effect.fromResult(prepareEntry(input, op));
|
|
1909
2069
|
const optionsOp = preparedOp(prepared, op);
|
|
@@ -1919,7 +2079,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1919
2079
|
return { reference: referenceFor(child), directory };
|
|
1920
2080
|
}));
|
|
1921
2081
|
}),
|
|
1922
|
-
symlink: Effect.
|
|
2082
|
+
symlink: Effect.fnUntraced(function* (target, input, options = {}) {
|
|
1923
2083
|
const op = OpContext.make("symlink");
|
|
1924
2084
|
const targetOp = op.at(target);
|
|
1925
2085
|
const prepared = yield* Effect.fromResult(prepareEntry(input, op));
|
|
@@ -1938,7 +2098,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1938
2098
|
return { reference: referenceFor(child), directory };
|
|
1939
2099
|
}));
|
|
1940
2100
|
}),
|
|
1941
|
-
link: Effect.
|
|
2101
|
+
link: Effect.fnUntraced(function* (sourceInput, input) {
|
|
1942
2102
|
const op = OpContext.make("link");
|
|
1943
2103
|
const source = asTarget(sourceInput);
|
|
1944
2104
|
const prepared = yield* Effect.fromResult(prepareEntry(input, op));
|
|
@@ -1954,14 +2114,14 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1954
2114
|
return { reference: referenceFor(node.ino), directory };
|
|
1955
2115
|
}));
|
|
1956
2116
|
}),
|
|
1957
|
-
unlink: Effect.
|
|
2117
|
+
unlink: Effect.fnUntraced(function* (input) {
|
|
1958
2118
|
return yield* entryVerb("unlink", input, unlinkEntry);
|
|
1959
2119
|
}),
|
|
1960
|
-
rmdir: Effect.
|
|
2120
|
+
rmdir: Effect.fnUntraced(function* (input) {
|
|
1961
2121
|
return yield* entryVerb("rmdir", input, rmdirEntry);
|
|
1962
2122
|
}),
|
|
1963
2123
|
remove,
|
|
1964
|
-
rename: Effect.
|
|
2124
|
+
rename: Effect.fnUntraced(function* (fromInput, toInput) {
|
|
1965
2125
|
const op = OpContext.make("rename");
|
|
1966
2126
|
const from = yield* Effect.fromResult(prepareEntry(fromInput, op));
|
|
1967
2127
|
const to = yield* Effect.fromResult(prepareEntry(toInput, op));
|
|
@@ -1971,34 +2131,34 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
1971
2131
|
return yield* renameEntry(source, destination, op);
|
|
1972
2132
|
}));
|
|
1973
2133
|
}),
|
|
1974
|
-
chmod: Effect.
|
|
2134
|
+
chmod: Effect.fnUntraced(function* (input, mode) {
|
|
1975
2135
|
const op = OpContext.make("chmod");
|
|
1976
2136
|
const target = asTarget(input);
|
|
1977
2137
|
if (!isMode(mode))
|
|
1978
2138
|
return yield* op.fail("InvalidArgument");
|
|
1979
2139
|
yield* changeAttributes(() => asResolvedNode(target, op), { mode }, op);
|
|
1980
2140
|
}),
|
|
1981
|
-
chown: Effect.
|
|
2141
|
+
chown: Effect.fnUntraced(function* (input, owner) {
|
|
1982
2142
|
const op = OpContext.make("chown");
|
|
1983
2143
|
const target = asTarget(input);
|
|
1984
2144
|
const decoded = yield* decodeOwnerUpdate(owner).pipe(Effect.mapError((cause) => fail(op, cause)));
|
|
1985
2145
|
yield* changeAttributes(() => asResolvedNode(target, op), { owner: decoded }, op);
|
|
1986
2146
|
}),
|
|
1987
|
-
utimes: Effect.
|
|
2147
|
+
utimes: Effect.fnUntraced(function* (input, times) {
|
|
1988
2148
|
const op = OpContext.make("utimes");
|
|
1989
2149
|
const target = asTarget(input);
|
|
1990
2150
|
const decoded = yield* decodeTimes(times).pipe(Effect.mapError((cause) => fail(op, cause)));
|
|
1991
2151
|
yield* changeAttributes(() => asResolvedNode(target, op), { times: decoded }, op);
|
|
1992
2152
|
}),
|
|
1993
2153
|
// A negative length fails before the target resolves, as truncate(2) rejects it before the lookup.
|
|
1994
|
-
truncate: Effect.
|
|
2154
|
+
truncate: Effect.fnUntraced(function* (input, length) {
|
|
1995
2155
|
const op = OpContext.make("truncate");
|
|
1996
2156
|
const target = asTarget(input);
|
|
1997
2157
|
if (!isLength(length))
|
|
1998
2158
|
return yield* op.fail("InvalidArgument");
|
|
1999
2159
|
yield* changeAttributes(() => asResolvedNode(target, op), { size: length }, op);
|
|
2000
2160
|
}),
|
|
2001
|
-
setattr: Effect.
|
|
2161
|
+
setattr: Effect.fnUntraced(function* (input, attributes) {
|
|
2002
2162
|
const op = OpContext.make("setattr");
|
|
2003
2163
|
const target = asTarget(input);
|
|
2004
2164
|
// A caller outside TypeScript can pass anything, so a non-object fails typed rather than as a defect;
|
|
@@ -2024,11 +2184,15 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
2024
2184
|
: yield* decodeExpected(attributes.expected).pipe(Effect.mapError((cause) => fail(op, cause, "expected")));
|
|
2025
2185
|
yield* changeAttributes(() => asResolvedNode(target, op), { size: attributes.size, mode: attributes.mode, owner, times, expected }, op);
|
|
2026
2186
|
}),
|
|
2027
|
-
|
|
2187
|
+
withRoot: Effect.fnUntraced(function* (input) {
|
|
2188
|
+
const acquired = yield* acquireDirectory(input, OpContext.make("withRoot"));
|
|
2189
|
+
return createCaller(acquired, identity, umask, [...roots, TokenRegistry.inode(acquired)], acquired.boundaries ?? []);
|
|
2190
|
+
}),
|
|
2191
|
+
withDirectory: Effect.fnUntraced(function* (input) {
|
|
2028
2192
|
const acquired = yield* acquireDirectory(input, OpContext.make("withDirectory"));
|
|
2029
|
-
return createCaller(acquired, identity, umask);
|
|
2193
|
+
return createCaller(acquired, identity, umask, acquired.namespaceRoots ?? roots, acquired.boundaries ?? []);
|
|
2030
2194
|
}),
|
|
2031
|
-
openDirectory: Effect.
|
|
2195
|
+
openDirectory: Effect.fnUntraced(function* (input) {
|
|
2032
2196
|
const acquired = yield* acquireDirectory(input, OpContext.make("openDirectory"));
|
|
2033
2197
|
const statOp = OpContext.make("stat");
|
|
2034
2198
|
const handle = Object.freeze({
|
|
@@ -2036,16 +2200,18 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
2036
2200
|
stat: coordinatedRead(statOp, Effect.gen(function* () {
|
|
2037
2201
|
const ino = TokenRegistry.inode(acquired);
|
|
2038
2202
|
const node = ino === undefined ? undefined : yield* view(ino);
|
|
2203
|
+
if (node !== undefined && acquired.authority !== undefined)
|
|
2204
|
+
yield* acquired.authority(node, statOp);
|
|
2039
2205
|
return yield* (node === undefined
|
|
2040
2206
|
? Effect.fail(statOp.fail("InvalidHandle"))
|
|
2041
2207
|
: Effect.succeed(withMetadata(node)));
|
|
2042
|
-
}))
|
|
2208
|
+
})),
|
|
2043
2209
|
close: coordinatedCleanup(Effect.suspend(() => {
|
|
2044
2210
|
if (TokenRegistry.inode(acquired) === undefined) {
|
|
2045
2211
|
return Effect.fail(OpContext.make("close").fail("InvalidHandle"));
|
|
2046
2212
|
}
|
|
2047
2213
|
return releaseDirectory(acquired);
|
|
2048
|
-
})).pipe(Effect.ensuring(closeReleasedScope(acquired))
|
|
2214
|
+
})).pipe(Effect.ensuring(closeReleasedScope(acquired)))
|
|
2049
2215
|
});
|
|
2050
2216
|
registry.registerDirectory(handle, acquired);
|
|
2051
2217
|
return handle;
|
|
@@ -2059,9 +2225,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
2059
2225
|
identity,
|
|
2060
2226
|
incarnation,
|
|
2061
2227
|
limits,
|
|
2062
|
-
usage: coordinatedRead(OpContext.make("usage"), Effect.sync(() => ({ usedBytes: state.usedBytes, entries: state.entries })))
|
|
2063
|
-
|
|
2064
|
-
watch: Effect.fn("Volume.watch")(function* (options) {
|
|
2228
|
+
usage: coordinatedRead(OpContext.make("usage"), Effect.sync(() => ({ usedBytes: state.usedBytes, entries: state.entries }))),
|
|
2229
|
+
watch: Effect.fnUntraced(function* (options) {
|
|
2065
2230
|
const op = OpContext.make("watch");
|
|
2066
2231
|
const decoded = yield* Effect.fromResult(decodeConfiguration(WatchOptions, options === undefined ? {} : options, "watch"));
|
|
2067
2232
|
const recursive = decoded.recursive ?? true;
|
|
@@ -2075,8 +2240,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
2075
2240
|
const stream = yield* admit(op, watchHub.subscribe(select, seams.afterSubscribe));
|
|
2076
2241
|
return Stream.map(stream, (event) => event.change);
|
|
2077
2242
|
}),
|
|
2078
|
-
snapshot: coordinatedRead(OpContext.make("snapshot"), Effect.sync(() => Image.make(state)))
|
|
2079
|
-
referenceKey: Effect.
|
|
2243
|
+
snapshot: coordinatedRead(OpContext.make("snapshot"), Effect.sync(() => Image.make(state))),
|
|
2244
|
+
referenceKey: Effect.fnUntraced(function* (reference) {
|
|
2080
2245
|
const op = OpContext.make("referenceKey");
|
|
2081
2246
|
return yield* coordinatedRead(op, Effect.gen(function* () {
|
|
2082
2247
|
const node = yield* TokenRegistry.nodeOrFail(yield* registry.resolve(reference, "reference"), "reference", op);
|
|
@@ -2084,7 +2249,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
2084
2249
|
return { identity: identityBytes.slice(), epoch: epochBytes.slice(), ino, tag: keyTag(ino) };
|
|
2085
2250
|
}));
|
|
2086
2251
|
}),
|
|
2087
|
-
resolveReferenceKey: Effect.
|
|
2252
|
+
resolveReferenceKey: Effect.fnUntraced(function* (key) {
|
|
2088
2253
|
const op = OpContext.make("resolveReferenceKey");
|
|
2089
2254
|
return yield* coordinatedRead(op, Effect.gen(function* () {
|
|
2090
2255
|
if (!Schema.is(ReferenceKey)(key))
|
|
@@ -2101,7 +2266,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
2101
2266
|
return referenceFor(ino);
|
|
2102
2267
|
}));
|
|
2103
2268
|
}),
|
|
2104
|
-
caller: Effect.
|
|
2269
|
+
caller: Effect.fnUntraced(function* (options) {
|
|
2105
2270
|
const decoded = yield* Effect.fromResult(decodeConfiguration(RootCallerOptions, options === undefined ? {} : options, "caller"));
|
|
2106
2271
|
const chosen = decoded.identity ?? { uid: 0, gid: 0, groups: [], privileged: true };
|
|
2107
2272
|
const identity = Object.freeze({ ...chosen, groups: Object.freeze([...chosen.groups]) });
|
|
@@ -2115,7 +2280,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, commitProvider, o
|
|
|
2115
2280
|
return Object.freeze({ volume, shutdown, observe });
|
|
2116
2281
|
});
|
|
2117
2282
|
/** @internal */
|
|
2118
|
-
export const make = Effect.
|
|
2283
|
+
export const make = Effect.fnUntraced(function* (options) {
|
|
2119
2284
|
return (yield* makeVolume(VolumeSource.Empty(), memoryCommitProvider, options).pipe(Effect.catchIf((error) => Schema.is(VfsError)(error) && error.code !== "InvalidArgument", Effect.die))).volume;
|
|
2120
2285
|
});
|
|
2121
2286
|
/** @internal */
|
|
@@ -2154,22 +2319,22 @@ const changeOptions = (options) => {
|
|
|
2154
2319
|
return Effect.fromResult(decodeConfiguration(OverlayChangesOptions, options === undefined ? {} : options, "changes"));
|
|
2155
2320
|
};
|
|
2156
2321
|
/** @internal */
|
|
2157
|
-
export const fromSnapshot = Effect.
|
|
2322
|
+
export const fromSnapshot = Effect.fnUntraced(function* (snapshot, options) {
|
|
2158
2323
|
return (yield* makeVolume(VolumeSource.Restored({ value: yield* Image.valueOf(snapshot) }), memoryCommitProvider, options)).volume;
|
|
2159
2324
|
}, Effect.mapError((error) => retargetFailure("fromSnapshot", error)));
|
|
2160
2325
|
/** @internal */
|
|
2161
|
-
export const makeOverlay = Effect.
|
|
2326
|
+
export const makeOverlay = Effect.fnUntraced(function* (base, options) {
|
|
2162
2327
|
const value = yield* Image.valueOf(base);
|
|
2163
2328
|
const made = yield* Effect.mapError(makeVolume(VolumeSource.Restored({ value }), memoryCommitProvider, options), (error) => retargetFailure("makeOverlay", error));
|
|
2164
2329
|
const baseObservation = yield* observeChanges(value);
|
|
2165
2330
|
const overlay = Object.freeze({
|
|
2166
2331
|
...made.volume,
|
|
2167
|
-
changes: Effect.
|
|
2332
|
+
changes: Effect.fnUntraced(function* (options) {
|
|
2168
2333
|
const selected = yield* changeOptions(options);
|
|
2169
2334
|
const current = yield* made.observe.changes;
|
|
2170
2335
|
return publicChanges(compareOverlay(baseObservation, current, selected.includeTimestamps ?? false));
|
|
2171
2336
|
}),
|
|
2172
|
-
capture: Effect.
|
|
2337
|
+
capture: Effect.fnUntraced(function* (options) {
|
|
2173
2338
|
const selected = yield* changeOptions(options);
|
|
2174
2339
|
const current = yield* made.observe.capture;
|
|
2175
2340
|
return Object.freeze({
|