@effect-vfs/core 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/canonicalBase64.js +3 -3
- package/dist/internal/overlayDiff.js +2 -2
- package/dist/internal/path.d.ts.map +1 -1
- package/dist/internal/path.js +3 -3
- package/dist/internal/snapshotDelta.js +2 -2
- package/dist/internal/tree.js +2 -2
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +433 -388
- package/package.json +6 -6
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as Arr from "effect/Array";
|
|
2
2
|
import * as ByteSize from "effect/ByteSize";
|
|
3
3
|
import * as Clock from "effect/Clock";
|
|
4
|
+
import * as Context from "effect/Context";
|
|
4
5
|
import * as Crypto from "effect/Crypto";
|
|
5
6
|
import * as Data from "effect/Data";
|
|
6
7
|
import * as Effect from "effect/Effect";
|
|
7
|
-
import * as
|
|
8
|
+
import * as Hex from "effect/encoding/Hex";
|
|
8
9
|
import * as Exit from "effect/Exit";
|
|
9
10
|
import * as Match from "effect/Match";
|
|
11
|
+
import * as MutableRef from "effect/MutableRef";
|
|
10
12
|
import * as Option from "effect/Option";
|
|
11
13
|
import * as Predicate from "effect/Predicate";
|
|
12
14
|
import * as Result from "effect/Result";
|
|
@@ -84,100 +86,115 @@ const encoder = new TextEncoder();
|
|
|
84
86
|
const accessDue = (metadata, now) => now !== metadata.atimeNs && (metadata.atimeNs <= metadata.mtimeNs || metadata.atimeNs <= metadata.ctimeNs ||
|
|
85
87
|
now - metadata.atimeNs >= RELATIME_INTERVAL_NS);
|
|
86
88
|
const refreshDue = (read) => read.access !== undefined && accessDue(read.access.node.metadata, read.access.now);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
89
|
+
class Draft extends Context.Service()("@effect-vfs/core/internal/virtualFileSystem/Draft", {
|
|
90
|
+
make: Effect.fnUntraced(function* (base) {
|
|
91
|
+
const revision = base.revision + 1n;
|
|
92
|
+
const cell = MutableRef.make({
|
|
93
|
+
owner: Symbol(),
|
|
94
|
+
pending: new Map(),
|
|
95
|
+
opens: undefined,
|
|
96
|
+
nextInode: base.nextInode,
|
|
97
|
+
changed: false,
|
|
98
|
+
finished: undefined,
|
|
99
|
+
entries: base.entries,
|
|
100
|
+
usedBytes: base.usedBytes,
|
|
101
|
+
events: [],
|
|
102
|
+
after: [],
|
|
103
|
+
removed: []
|
|
104
|
+
});
|
|
105
|
+
const get = (ino) => Effect.sync(() => {
|
|
106
|
+
const draft = MutableRef.get(cell);
|
|
107
|
+
return draft.pending.has(ino) ? draft.pending.get(ino) : InodeTable.get(base.inodes, ino);
|
|
108
|
+
});
|
|
109
|
+
const openCount = (ino) => Effect.sync(() => MutableRef.get(cell).opens?.get(ino) ?? base.open.get(ino) ?? 0);
|
|
110
|
+
const put = (node, changed) => Effect.sync(() => {
|
|
111
|
+
MutableRef.update(cell, (draft) => {
|
|
112
|
+
// This map belongs only to this transition. Copying it on every inode write makes recursive changes quadratic.
|
|
113
|
+
draft.pending.set(node.ino, changed ? { ...node, revision } : node);
|
|
114
|
+
return { ...draft, changed: draft.changed || changed };
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
revision,
|
|
119
|
+
get,
|
|
120
|
+
put: (node) => put(node, true),
|
|
121
|
+
putQuiet: (node) => put(node, false),
|
|
122
|
+
unchanged: Effect.sync(() => {
|
|
123
|
+
const draft = MutableRef.get(cell);
|
|
124
|
+
return draft.pending.size === 0 && draft.opens === undefined && draft.nextInode === base.nextInode &&
|
|
125
|
+
draft.entries === base.entries && draft.usedBytes === base.usedBytes;
|
|
126
|
+
}),
|
|
127
|
+
remove: (ino) => Effect.sync(() => {
|
|
128
|
+
const draft = MutableRef.get(cell);
|
|
129
|
+
draft.pending.set(ino, undefined);
|
|
130
|
+
draft.removed.push(ino);
|
|
131
|
+
}),
|
|
132
|
+
allocate: Effect.sync(() => {
|
|
133
|
+
const next = MutableRef.get(cell).nextInode;
|
|
134
|
+
MutableRef.update(cell, (draft) => ({ ...draft, nextInode: Ino(next + 1) }));
|
|
135
|
+
return next;
|
|
136
|
+
}),
|
|
137
|
+
canAllocate: Effect.sync(() => MutableRef.get(cell).nextInode < Number.MAX_SAFE_INTEGER),
|
|
138
|
+
openCount,
|
|
139
|
+
retain: (ino) => Effect.sync(() => {
|
|
140
|
+
const count = MutableRef.get(cell).opens?.get(ino) ?? base.open.get(ino) ?? 0;
|
|
141
|
+
MutableRef.update(cell, (draft) => ({ ...draft, opens: new Map(draft.opens).set(ino, count + 1) }));
|
|
142
|
+
}),
|
|
143
|
+
release: (ino) => Effect.sync(() => {
|
|
144
|
+
const count = (MutableRef.get(cell).opens?.get(ino) ?? base.open.get(ino) ?? 0) - 1;
|
|
145
|
+
MutableRef.update(cell, (draft) => ({ ...draft, opens: new Map(draft.opens).set(ino, Math.max(0, count)) }));
|
|
146
|
+
return count;
|
|
147
|
+
}),
|
|
148
|
+
entries: Effect.sync(() => MutableRef.get(cell).entries),
|
|
149
|
+
addEntries: (amount) => Effect.sync(() => {
|
|
150
|
+
MutableRef.update(cell, (draft) => ({ ...draft, entries: draft.entries + amount }));
|
|
151
|
+
}),
|
|
152
|
+
usedBytes: Effect.sync(() => MutableRef.get(cell).usedBytes),
|
|
153
|
+
addUsedBytes: (amount) => Effect.sync(() => {
|
|
154
|
+
MutableRef.update(cell, (draft) => ({ ...draft, usedBytes: draft.usedBytes + amount }));
|
|
155
|
+
}),
|
|
156
|
+
publish: (event) => Effect.sync(() => {
|
|
157
|
+
MutableRef.get(cell).events.push(event);
|
|
158
|
+
}),
|
|
159
|
+
afterInstall: (action) => Effect.sync(() => {
|
|
160
|
+
MutableRef.get(cell).after.push(action);
|
|
161
|
+
}),
|
|
162
|
+
finish: Effect.sync(() => {
|
|
163
|
+
const draft = MutableRef.get(cell);
|
|
164
|
+
if (draft.finished !== undefined)
|
|
165
|
+
return draft.finished;
|
|
166
|
+
let inodes = base.inodes;
|
|
167
|
+
for (const [ino, node] of draft.pending)
|
|
168
|
+
inodes = InodeTable.set(inodes, ino, node, draft.owner);
|
|
169
|
+
let open = base.open;
|
|
170
|
+
if (draft.opens !== undefined) {
|
|
171
|
+
const next = new Map(base.open);
|
|
172
|
+
for (const [ino, count] of draft.opens) {
|
|
173
|
+
if (count > 0)
|
|
174
|
+
next.set(ino, count);
|
|
175
|
+
else
|
|
176
|
+
next.delete(ino);
|
|
177
|
+
}
|
|
178
|
+
open = next;
|
|
179
|
+
}
|
|
180
|
+
const finished = {
|
|
181
|
+
inodes,
|
|
182
|
+
open,
|
|
183
|
+
nextInode: draft.nextInode,
|
|
184
|
+
revision: draft.changed ? revision : base.revision,
|
|
185
|
+
entries: draft.entries,
|
|
186
|
+
usedBytes: draft.usedBytes
|
|
187
|
+
};
|
|
188
|
+
MutableRef.update(cell, (current) => ({ ...current, finished }));
|
|
189
|
+
return finished;
|
|
190
|
+
}),
|
|
191
|
+
actions: Effect.sync(() => {
|
|
192
|
+
const { events, after, removed } = MutableRef.get(cell);
|
|
193
|
+
return { events, after, removed };
|
|
194
|
+
})
|
|
178
195
|
};
|
|
179
|
-
|
|
180
|
-
|
|
196
|
+
})
|
|
197
|
+
}) {
|
|
181
198
|
}
|
|
182
199
|
const objectReferences = new WeakMap();
|
|
183
200
|
const isFileHandle = (value) => Predicate.hasProperty(FileHandleId)(value);
|
|
@@ -227,7 +244,7 @@ const observeChanges = Effect.fnUntraced(function* (captured) {
|
|
|
227
244
|
});
|
|
228
245
|
const randomHex128 = Effect.gen(function* () {
|
|
229
246
|
const crypto = yield* Crypto.Crypto;
|
|
230
|
-
return
|
|
247
|
+
return Hex.encode(yield* crypto.randomBytes(16));
|
|
231
248
|
}).pipe(Effect.orDie);
|
|
232
249
|
/** @internal */
|
|
233
250
|
export const makeVolume = Effect.fnUntraced(function* (source, options, commitProvider, captureInitial, durability = "memory-only") {
|
|
@@ -256,9 +273,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
256
273
|
const incarnation = VolumeIncarnation.make(yield* randomHex128);
|
|
257
274
|
const epoch = live === undefined ? VolumeEpoch.make(yield* randomHex128) : live.epoch;
|
|
258
275
|
const keySecret = live === undefined ? KeySecret.make(yield* randomHex128) : live.keySecret;
|
|
259
|
-
const identityBytes = Result.getOrThrow(
|
|
260
|
-
const epochBytes = Result.getOrThrow(
|
|
261
|
-
const keySecretBytes = Result.getOrThrow(
|
|
276
|
+
const identityBytes = Result.getOrThrow(Hex.decode(identity));
|
|
277
|
+
const epochBytes = Result.getOrThrow(Hex.decode(epoch));
|
|
278
|
+
const keySecretBytes = Result.getOrThrow(Hex.decode(keySecret));
|
|
262
279
|
const clock = yield* Clock.clockWith(Effect.succeed);
|
|
263
280
|
const initialTime = clock.currentTimeNanosUnsafe();
|
|
264
281
|
if (!isTimestamp(initialTime)) {
|
|
@@ -339,91 +356,76 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
339
356
|
const initialImage = captureInitial === undefined
|
|
340
357
|
? undefined
|
|
341
358
|
: yield* captureInitial(state, { identity, epoch, keySecret }, limits);
|
|
342
|
-
|
|
343
|
-
const view = (ino) => draft === undefined ? getNode(state, ino) : draft.get(ino);
|
|
359
|
+
const view = (ino) => Effect.flatMap(Effect.serviceOption(Draft), (draft) => Option.isSome(draft) ? draft.value.get(ino) : Effect.sync(() => getNode(state, ino)));
|
|
344
360
|
let available = true;
|
|
345
361
|
const checkAvailable = (operation) => Effect.suspend(() => available ? Effect.void : Effect.fail(fsFailure("VolumeUnavailable", operation)));
|
|
346
362
|
const watchCoordinate = (effect) => changing(effect);
|
|
347
363
|
const watchHub = yield* WatchHub.make(watchCoordinate, settings.maxWatchEvents ?? 256);
|
|
348
|
-
const install = (finished)
|
|
364
|
+
const install = Effect.fnUntraced(function* (finished) {
|
|
349
365
|
const before = state;
|
|
350
|
-
state = finished.finish
|
|
351
|
-
|
|
366
|
+
state = yield* finished.finish;
|
|
367
|
+
const actions = yield* finished.actions;
|
|
368
|
+
for (const ino of actions.removed)
|
|
352
369
|
tokens.delete(ino);
|
|
353
|
-
for (const apply of
|
|
370
|
+
for (const apply of actions.after)
|
|
354
371
|
apply();
|
|
355
372
|
const after = state;
|
|
356
|
-
watchHub.publishUnsafe(() =>
|
|
357
|
-
};
|
|
358
|
-
const transition =
|
|
359
|
-
const current =
|
|
360
|
-
|
|
361
|
-
return
|
|
362
|
-
|
|
363
|
-
|
|
373
|
+
watchHub.publishUnsafe(() => actions.events.flatMap((events) => Array.from(events(after))), { before, after });
|
|
374
|
+
});
|
|
375
|
+
const transition = Effect.fnUntraced(function* (change) {
|
|
376
|
+
const current = yield* Draft.make(state);
|
|
377
|
+
const value = yield* Effect.provideService(change, Draft, current);
|
|
378
|
+
return [value, current];
|
|
379
|
+
});
|
|
380
|
+
const applyDirect = Effect.fnUntraced(function* (change) {
|
|
381
|
+
const current = yield* Draft.make(state);
|
|
382
|
+
yield* Effect.provideService(change(current), Draft, current);
|
|
383
|
+
yield* install(current);
|
|
364
384
|
});
|
|
365
|
-
const applyDirect = (change) => {
|
|
366
|
-
const current = new Draft(state);
|
|
367
|
-
draft = current;
|
|
368
|
-
try {
|
|
369
|
-
change(current);
|
|
370
|
-
}
|
|
371
|
-
finally {
|
|
372
|
-
draft = undefined;
|
|
373
|
-
}
|
|
374
|
-
install(current);
|
|
375
|
-
};
|
|
376
385
|
// Offers the finished draft's value to the store before installing it. A rejected candidate is discarded and
|
|
377
386
|
// the volume stays available; an uncertain answer stops the volume. A change that is a cleanup runs its
|
|
378
387
|
// release even when the store refuses, since the handle must not stay open.
|
|
379
388
|
const committed = Effect.fnUntraced(function* (op, provider, finished, onStorageFailure) {
|
|
380
|
-
if (finished.unchanged)
|
|
381
|
-
return install(finished);
|
|
382
|
-
const next = finished.finish
|
|
389
|
+
if (yield* finished.unchanged)
|
|
390
|
+
return yield* install(finished);
|
|
391
|
+
const next = yield* finished.finish;
|
|
383
392
|
if (provider.prepare !== undefined)
|
|
384
393
|
yield* provider.prepare(next);
|
|
385
394
|
const answer = yield* offerCommit(provider, op.operation, next, onStorageFailure !== undefined);
|
|
386
395
|
if (!answer.available)
|
|
387
396
|
available = false;
|
|
388
397
|
if (answer.failure !== undefined) {
|
|
389
|
-
if (!answer.available)
|
|
390
|
-
onStorageFailure
|
|
398
|
+
if (!answer.available && onStorageFailure !== undefined)
|
|
399
|
+
yield* onStorageFailure();
|
|
391
400
|
return yield* answer.failure;
|
|
392
401
|
}
|
|
393
|
-
install(finished);
|
|
402
|
+
yield* install(finished);
|
|
394
403
|
});
|
|
395
404
|
const annotateFailure = (error) => Effect.annotateCurrentSpan({ operation: error.operation, code: error.code });
|
|
396
405
|
// Permit waits stay interruptible. A change and its publication run under every permit; with a store, the
|
|
397
406
|
// change itself stays interruptible and only the commit and installation are not.
|
|
398
407
|
const coordinated = (op, effect, onStorageFailure) => admit(op, changing(commitProvider === undefined
|
|
399
|
-
? Effect.uninterruptible(Effect.
|
|
400
|
-
install(finished);
|
|
401
|
-
return value;
|
|
402
|
-
}))
|
|
408
|
+
? Effect.uninterruptible(Effect.flatMap(transition(Effect.andThen(checkAvailable(op.operation), effect)), ([value, finished]) => Effect.as(install(finished), value)))
|
|
403
409
|
: Effect.uninterruptibleMask((restore) => Effect.flatMap(restore(transition(Effect.andThen(checkAvailable(op.operation), effect))), ([value, finished]) => Effect.as(committed(op, commitProvider, finished, onStorageFailure), value))))).pipe(Effect.tapError((error) => Schema.is(VfsError)(error) ? annotateFailure(error) : Effect.void));
|
|
404
410
|
const coordinatedRead = (op, effect) => admit(op, observing(Effect.andThen(checkAvailable(op.operation), effect))).pipe(Effect.tapError((error) => Schema.is(VfsError)(error) ? annotateFailure(error) : Effect.void));
|
|
405
|
-
const refreshAccess = (read)
|
|
411
|
+
const refreshAccess = Effect.fnUntraced(function* (read) {
|
|
406
412
|
if (refreshDue(read)) {
|
|
407
413
|
const { node, now } = read.access;
|
|
408
|
-
|
|
414
|
+
const draft = yield* Draft;
|
|
415
|
+
yield* draft.putQuiet({ ...node, metadata: { ...node.metadata, atimeNs: now } });
|
|
409
416
|
}
|
|
410
417
|
return read.value;
|
|
411
|
-
};
|
|
418
|
+
});
|
|
412
419
|
// A read that may refresh an access time. It observes under one permit, so reads run beside each other, and
|
|
413
420
|
// only when the access time is due does it run again as a change. The change repeats every check and the rule,
|
|
414
421
|
// since another read may have refreshed the time meanwhile, and its result is the one returned.
|
|
415
422
|
const accessing = (op, read) => Effect.flatMap(coordinatedRead(op, read), (observed) => refreshDue(observed)
|
|
416
|
-
? coordinated(op, Effect.
|
|
423
|
+
? coordinated(op, Effect.flatMap(read, refreshAccess))
|
|
417
424
|
: Effect.succeed(observed.value));
|
|
418
425
|
const coordinatedCleanup = (effect) => changing(Effect.uninterruptible(effect));
|
|
419
426
|
const shutdown = commitProvider === undefined ? undefined : changing(Effect.sync(() => {
|
|
420
427
|
available = false;
|
|
421
428
|
}));
|
|
422
|
-
const current = () => {
|
|
423
|
-
if (draft === undefined)
|
|
424
|
-
return dieInvariant("Volume mutation outside a transition");
|
|
425
|
-
return draft;
|
|
426
|
-
};
|
|
427
429
|
const pathOf = (get, ino) => {
|
|
428
430
|
const names = [];
|
|
429
431
|
let node = get(ino);
|
|
@@ -438,19 +440,33 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
438
440
|
}
|
|
439
441
|
return SLASH_HEX + names.reverse().join(SLASH_HEX);
|
|
440
442
|
};
|
|
443
|
+
const pathOfCurrent = Effect.fnUntraced(function* (ino) {
|
|
444
|
+
const names = [];
|
|
445
|
+
const first = yield* view(ino);
|
|
446
|
+
if (first?.kind !== "directory" || first.metadata.nlink === 0)
|
|
447
|
+
return undefined;
|
|
448
|
+
let node = first;
|
|
449
|
+
while (node.ino !== ROOT_INO) {
|
|
450
|
+
names.push(node.name);
|
|
451
|
+
const parent = yield* view(node.parent);
|
|
452
|
+
if (parent?.kind !== "directory" || parent.metadata.nlink === 0)
|
|
453
|
+
return undefined;
|
|
454
|
+
node = parent;
|
|
455
|
+
}
|
|
456
|
+
return SLASH_HEX + names.reverse().join(SLASH_HEX);
|
|
457
|
+
});
|
|
441
458
|
const entryPath = (prefix, name) => ownedPath(nameBytes(prefix + (prefix === SLASH_HEX ? "" : SLASH_HEX) + name));
|
|
442
|
-
const publishEntry = (_tag, parent, name, ino) => {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const target = current().get(ino);
|
|
459
|
+
const publishEntry = (_tag, parent, name, ino) => Effect.flatMap(Draft, (draft) => draft.publish((installed) => {
|
|
460
|
+
const prefix = pathOf((at) => getNode(installed, at), parent);
|
|
461
|
+
return prefix === undefined ? [] : [{ change: { _tag, path: entryPath(prefix, name) }, parent, ino }];
|
|
462
|
+
}));
|
|
463
|
+
const publishNode = Effect.fnUntraced(function* (ino) {
|
|
464
|
+
const draft = yield* Draft;
|
|
465
|
+
const target = yield* draft.get(ino);
|
|
450
466
|
if (target === undefined)
|
|
451
467
|
return;
|
|
452
468
|
if (target.kind === "directory") {
|
|
453
|
-
|
|
469
|
+
yield* draft.publish((installed) => {
|
|
454
470
|
const directory = getNode(installed, ino);
|
|
455
471
|
const path = pathOf((at) => getNode(installed, at), ino);
|
|
456
472
|
return path === undefined || directory?.kind !== "directory"
|
|
@@ -460,7 +476,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
460
476
|
return;
|
|
461
477
|
}
|
|
462
478
|
const links = target.links;
|
|
463
|
-
|
|
479
|
+
yield* draft.publish((installed) => {
|
|
464
480
|
const events = [];
|
|
465
481
|
for (const link of links) {
|
|
466
482
|
const prefix = pathOf((at) => getNode(installed, at), link.parent);
|
|
@@ -474,7 +490,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
474
490
|
}
|
|
475
491
|
return events;
|
|
476
492
|
});
|
|
477
|
-
};
|
|
493
|
+
});
|
|
478
494
|
const pathsOf = (installed, ino) => {
|
|
479
495
|
const node = getNode(installed, ino);
|
|
480
496
|
if (node === undefined)
|
|
@@ -573,7 +589,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
573
589
|
const holdDirectory = (ino) => {
|
|
574
590
|
directoryHolds.set(ino, (directoryHolds.get(ino) ?? 0) + 1);
|
|
575
591
|
};
|
|
576
|
-
const unholdDirectory = (ino)
|
|
592
|
+
const unholdDirectory = Effect.fnUntraced(function* (ino) {
|
|
577
593
|
const count = (directoryHolds.get(ino) ?? 1) - 1;
|
|
578
594
|
if (count > 0) {
|
|
579
595
|
directoryHolds.set(ino, count);
|
|
@@ -581,19 +597,20 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
581
597
|
}
|
|
582
598
|
directoryHolds.delete(ino);
|
|
583
599
|
const node = getNode(state, ino);
|
|
584
|
-
if (node?.kind === "directory" && node.metadata.nlink === 0)
|
|
585
|
-
applyDirect((d) => d.remove(ino));
|
|
586
|
-
|
|
587
|
-
|
|
600
|
+
if (node?.kind === "directory" && node.metadata.nlink === 0) {
|
|
601
|
+
yield* applyDirect((d) => d.remove(ino));
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
const releaseDirectory = Effect.fnUntraced(function* (reference) {
|
|
588
605
|
const ino = reference.ino;
|
|
589
606
|
reference.ino = undefined;
|
|
590
607
|
reference.closed = true;
|
|
591
608
|
if (ino !== undefined)
|
|
592
|
-
unholdDirectory(ino);
|
|
593
|
-
};
|
|
609
|
+
yield* unholdDirectory(ino);
|
|
610
|
+
});
|
|
594
611
|
const finalizeDirectory = (reference) => Effect.uninterruptible(Effect.suspend(() => reference.closed
|
|
595
612
|
? Effect.void
|
|
596
|
-
: coordinatedCleanup(
|
|
613
|
+
: coordinatedCleanup(releaseDirectory(reference))));
|
|
597
614
|
const closeReleasedScope = (reference) => Effect.suspend(() => !reference.closed || reference.scope === undefined ? Effect.void : Scope.close(reference.scope, Exit.void));
|
|
598
615
|
const permitted = (node, identity) => {
|
|
599
616
|
const metadata = node.metadata;
|
|
@@ -607,33 +624,45 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
607
624
|
const authorize = (node, identity, bits, op) => identity.privileged || (permitted(node, identity) & bits) === bits
|
|
608
625
|
? Effect.void
|
|
609
626
|
: Effect.fail(op.fail("AccessDenied"));
|
|
610
|
-
const reclaim = (d, ino)
|
|
611
|
-
const node = d.get(ino);
|
|
627
|
+
const reclaim = Effect.fnUntraced(function* (d, ino) {
|
|
628
|
+
const node = yield* d.get(ino);
|
|
612
629
|
if (node === undefined)
|
|
613
630
|
return;
|
|
614
631
|
if (node.kind === "file") {
|
|
615
|
-
if (node.links.length > 0 || d.openCount(ino) > 0)
|
|
632
|
+
if (node.links.length > 0 || (yield* d.openCount(ino)) > 0)
|
|
616
633
|
return;
|
|
617
|
-
d.
|
|
634
|
+
yield* d.addUsedBytes(-BigInt(node.data.length));
|
|
618
635
|
}
|
|
619
636
|
else if (node.kind === "symlink") {
|
|
620
637
|
if (node.links.length > 0)
|
|
621
638
|
return;
|
|
622
|
-
d.
|
|
639
|
+
yield* d.addUsedBytes(-BigInt(node.target.length));
|
|
623
640
|
}
|
|
624
641
|
else if (node.metadata.nlink > 0 || directoryHolds.has(ino))
|
|
625
642
|
return;
|
|
626
|
-
d.remove(ino);
|
|
627
|
-
};
|
|
628
|
-
const atEntryLimit = (
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
643
|
+
yield* d.remove(ino);
|
|
644
|
+
});
|
|
645
|
+
const atEntryLimit = Effect.gen(function* () {
|
|
646
|
+
const draft = yield* Draft;
|
|
647
|
+
return settings.maxEntries !== undefined && (yield* draft.entries) >= settings.maxEntries;
|
|
648
|
+
});
|
|
649
|
+
const reserveEntry = Effect.fnUntraced(function* (op) {
|
|
650
|
+
const draft = yield* Draft;
|
|
651
|
+
if ((yield* atEntryLimit) || !(yield* draft.canAllocate))
|
|
652
|
+
return yield* op.fail("NoSpace");
|
|
653
|
+
});
|
|
654
|
+
const reserveInode = (op) => Effect.flatMap(Draft, (draft) => Effect.flatMap(draft.canAllocate, (available) => available ? Effect.void : Effect.fail(op.fail("NoSpace"))));
|
|
655
|
+
const reserveBytes = Effect.fnUntraced(function* (op, bytes) {
|
|
656
|
+
if (settings.maxBytes === undefined)
|
|
657
|
+
return;
|
|
658
|
+
const draft = yield* Draft;
|
|
659
|
+
if (bytes > ByteSize.toBigInt(settings.maxBytes) - (yield* draft.usedBytes)) {
|
|
660
|
+
return yield* op.fail("NoSpace");
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
const attach = Effect.fnUntraced(function* (parent, name, child, now) {
|
|
664
|
+
const d = yield* Draft;
|
|
665
|
+
yield* d.put(withEntries({
|
|
637
666
|
...parent,
|
|
638
667
|
metadata: {
|
|
639
668
|
...parent.metadata,
|
|
@@ -643,51 +672,49 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
643
672
|
}
|
|
644
673
|
}, (entries) => entries.set(name, child.ino)));
|
|
645
674
|
if (child.kind === "directory")
|
|
646
|
-
d.put({ ...child, parent: parent.ino, name });
|
|
675
|
+
yield* d.put({ ...child, parent: parent.ino, name });
|
|
647
676
|
else {
|
|
648
|
-
d.put({
|
|
677
|
+
yield* d.put({
|
|
649
678
|
...child,
|
|
650
679
|
links: [...child.links, { parent: parent.ino, name }],
|
|
651
680
|
metadata: { ...child.metadata, nlink: child.links.length + 1, ctimeNs: now }
|
|
652
681
|
});
|
|
653
682
|
}
|
|
654
|
-
};
|
|
655
|
-
const detach = (child, parent, name, now)
|
|
656
|
-
const d =
|
|
683
|
+
});
|
|
684
|
+
const detach = Effect.fnUntraced(function* (child, parent, name, now) {
|
|
685
|
+
const d = yield* Draft;
|
|
657
686
|
if (child.kind === "directory") {
|
|
658
|
-
d.put({ ...child, metadata: { ...child.metadata, nlink: 0, ctimeNs: now } });
|
|
687
|
+
yield* d.put({ ...child, metadata: { ...child.metadata, nlink: 0, ctimeNs: now } });
|
|
659
688
|
}
|
|
660
689
|
else {
|
|
661
690
|
const links = withoutLink(child.links, parent, name);
|
|
662
|
-
d.put({ ...child, links, metadata: { ...child.metadata, nlink: links.length, ctimeNs: now } });
|
|
691
|
+
yield* d.put({ ...child, links, metadata: { ...child.metadata, nlink: links.length, ctimeNs: now } });
|
|
663
692
|
}
|
|
664
|
-
reclaim(d, child.ino);
|
|
665
|
-
};
|
|
666
|
-
const releaseFile = (ref)
|
|
693
|
+
yield* reclaim(d, child.ino);
|
|
694
|
+
});
|
|
695
|
+
const releaseFile = Effect.fnUntraced(function* (ref) {
|
|
667
696
|
const ino = ref.ino;
|
|
668
697
|
if (ino !== undefined) {
|
|
669
|
-
const d =
|
|
670
|
-
d.release(ino);
|
|
671
|
-
reclaim(d, ino);
|
|
698
|
+
const d = yield* Draft;
|
|
699
|
+
yield* d.release(ino);
|
|
700
|
+
yield* reclaim(d, ino);
|
|
672
701
|
}
|
|
673
702
|
ref.ino = undefined;
|
|
674
703
|
ref.closed = true;
|
|
675
|
-
};
|
|
676
|
-
const releaseOpenFile = (ref) => coordinatedCleanup(Effect.
|
|
677
|
-
if (!ref.closed)
|
|
678
|
-
applyDirect(() => releaseFile(ref));
|
|
679
|
-
}));
|
|
704
|
+
});
|
|
705
|
+
const releaseOpenFile = (ref) => coordinatedCleanup(Effect.suspend(() => ref.closed ? Effect.void : applyDirect(() => releaseFile(ref))));
|
|
680
706
|
// Explicit close and scope cleanup share one release. A release whose commit fails still completes as
|
|
681
707
|
// cleanup, so the handle never stays open; only an explicit close reports the failure. A close refused
|
|
682
708
|
// admission never entered the volume, so it leaves the handle open for a retry.
|
|
683
|
-
const closeFile = (ref, op, check) => coordinated(op, Effect.andThen(check, Effect.
|
|
709
|
+
const closeFile = (ref, op, check) => coordinated(op, Effect.andThen(check, Effect.gen(function* () {
|
|
684
710
|
const ino = ref.ino;
|
|
685
711
|
if (ino !== undefined) {
|
|
686
|
-
const d =
|
|
687
|
-
d.release(ino);
|
|
688
|
-
reclaim(d, ino);
|
|
712
|
+
const d = yield* Draft;
|
|
713
|
+
yield* d.release(ino);
|
|
714
|
+
yield* reclaim(d, ino);
|
|
689
715
|
}
|
|
690
|
-
|
|
716
|
+
const d = yield* Draft;
|
|
717
|
+
yield* d.afterInstall(() => {
|
|
691
718
|
ref.ino = undefined;
|
|
692
719
|
ref.closed = true;
|
|
693
720
|
});
|
|
@@ -713,19 +740,16 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
713
740
|
return yield* coordinate(Effect.suspend(() => closed() ? Effect.interrupt : acquire).pipe(Effect.tap(() => Effect.suspend(() => {
|
|
714
741
|
if (!closed())
|
|
715
742
|
return Effect.void;
|
|
716
|
-
releaseAcquired();
|
|
717
|
-
return Effect.interrupt;
|
|
743
|
+
return Effect.andThen(releaseAcquired(), Effect.interrupt);
|
|
718
744
|
})))).pipe(Effect.tap(() => Effect.suspend(() => closed() ? Effect.interrupt : Effect.void)), Effect.onError(() => Effect.andThen(finalize, Scope.close(scope, Exit.void))));
|
|
719
745
|
});
|
|
720
|
-
const bindFile = (ref, ino) => {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
d.usedBytes += BigInt(data.length - file.data.length);
|
|
728
|
-
d.put({
|
|
746
|
+
const bindFile = (ref, ino) => Effect.flatMap(Draft, (draft) => draft.afterInstall(() => {
|
|
747
|
+
ref.ino = ino;
|
|
748
|
+
}));
|
|
749
|
+
const replaceContent = Effect.fnUntraced(function* (file, data, now, publish = true) {
|
|
750
|
+
const d = yield* Draft;
|
|
751
|
+
yield* d.addUsedBytes(BigInt(data.length - file.data.length));
|
|
752
|
+
yield* d.put({
|
|
729
753
|
...file,
|
|
730
754
|
data,
|
|
731
755
|
metadata: {
|
|
@@ -737,8 +761,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
737
761
|
}
|
|
738
762
|
});
|
|
739
763
|
if (publish)
|
|
740
|
-
publishNode(file.ino);
|
|
741
|
-
};
|
|
764
|
+
yield* publishNode(file.ino);
|
|
765
|
+
});
|
|
742
766
|
const resizeAt = Effect.fnUntraced(function* (file, length, now, op) {
|
|
743
767
|
if (length > BigInt(maxFileBytes))
|
|
744
768
|
return yield* op.fail("FileTooLarge");
|
|
@@ -746,22 +770,22 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
746
770
|
yield* reserveBytes(op, BigInt(size - file.data.length));
|
|
747
771
|
const data = new Uint8Array(size);
|
|
748
772
|
data.set(file.data.subarray(0, size));
|
|
749
|
-
replaceContent(file, data, now, false);
|
|
773
|
+
yield* replaceContent(file, data, now, false);
|
|
750
774
|
});
|
|
751
775
|
const resize = Effect.fnUntraced(function* (file, length, op) {
|
|
752
776
|
if (!isLength(length))
|
|
753
777
|
return yield* op.fail("InvalidArgument");
|
|
754
778
|
yield* resizeAt(file, length, yield* timestamp(op), op);
|
|
755
|
-
publishNode(file.ino);
|
|
779
|
+
yield* publishNode(file.ino);
|
|
756
780
|
});
|
|
757
781
|
const fileHandle = (ref) => {
|
|
758
|
-
const get = (op, access)
|
|
759
|
-
const node = ref.ino === undefined ? undefined : view(ref.ino);
|
|
760
|
-
|
|
761
|
-
(access === "write" && ref.access === "read")
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
};
|
|
782
|
+
const get = Effect.fnUntraced(function* (op, access) {
|
|
783
|
+
const node = ref.ino === undefined ? undefined : yield* view(ref.ino);
|
|
784
|
+
if (node?.kind !== "file" || (access === "read" && ref.access === "write") ||
|
|
785
|
+
(access === "write" && ref.access === "read"))
|
|
786
|
+
return yield* op.fail("InvalidHandle");
|
|
787
|
+
return node;
|
|
788
|
+
});
|
|
765
789
|
const read = (maximum, position) => {
|
|
766
790
|
const op = OpContext.make(position === undefined ? "read" : "pread");
|
|
767
791
|
const readOp = OpContext.make("read");
|
|
@@ -783,9 +807,10 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
783
807
|
// other would start at the same offset.
|
|
784
808
|
if (position !== undefined)
|
|
785
809
|
return Effect.map(accessing(op, body), ({ bytes, eof }) => ({ bytes, eof }));
|
|
786
|
-
return coordinated(op, Effect.
|
|
787
|
-
const { bytes, eof, next } = refreshAccess(
|
|
788
|
-
|
|
810
|
+
return coordinated(op, Effect.gen(function* () {
|
|
811
|
+
const { bytes, eof, next } = yield* refreshAccess(yield* body);
|
|
812
|
+
const draft = yield* Draft;
|
|
813
|
+
yield* draft.afterInstall(() => {
|
|
789
814
|
ref.offset = next;
|
|
790
815
|
});
|
|
791
816
|
return { bytes, eof };
|
|
@@ -809,9 +834,10 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
809
834
|
if (offset >= BigInt(maxFileBytes))
|
|
810
835
|
return yield* writeOp.fail("FileTooLarge");
|
|
811
836
|
const start = Number(offset);
|
|
837
|
+
const draft = yield* Draft;
|
|
812
838
|
const free = settings.maxBytes === undefined
|
|
813
839
|
? BigInt(maxFileBytes)
|
|
814
|
-
: ByteSize.toBigInt(settings.maxBytes) -
|
|
840
|
+
: ByteSize.toBigInt(settings.maxBytes) - (yield* draft.usedBytes);
|
|
815
841
|
const maximumEnd = BigInt(file.data.length) + free;
|
|
816
842
|
const end = Number(BigInt(maxFileBytes) < maximumEnd ? BigInt(maxFileBytes) : maximumEnd);
|
|
817
843
|
const count = Math.min(bytes.length, Math.max(0, end - start));
|
|
@@ -824,10 +850,10 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
824
850
|
data.set(file.data);
|
|
825
851
|
const now = yield* timestamp(writeOp);
|
|
826
852
|
data.set(bytes.subarray(0, count), start);
|
|
827
|
-
replaceContent(file, data, now);
|
|
853
|
+
yield* replaceContent(file, data, now);
|
|
828
854
|
if (position === undefined) {
|
|
829
855
|
const next = offset + BigInt(count);
|
|
830
|
-
|
|
856
|
+
yield* draft.afterInstall(() => {
|
|
831
857
|
ref.offset = next;
|
|
832
858
|
});
|
|
833
859
|
}
|
|
@@ -901,35 +927,38 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
901
927
|
if (known.volume !== volumeIdentity) {
|
|
902
928
|
return yield* op.fail("ForeignReference");
|
|
903
929
|
}
|
|
904
|
-
const node = view(known.ino);
|
|
930
|
+
const node = yield* view(known.ino);
|
|
905
931
|
if (node === undefined || (node.kind === "directory" && node.metadata.nlink === 0)) {
|
|
906
932
|
return yield* op.fail("StaleReference");
|
|
907
933
|
}
|
|
908
934
|
return node;
|
|
909
935
|
});
|
|
910
|
-
const callerDirectory = (op)
|
|
911
|
-
const node = reference.ino === undefined ? undefined : view(reference.ino);
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
936
|
+
const callerDirectory = Effect.fnUntraced(function* (op) {
|
|
937
|
+
const node = reference.ino === undefined ? undefined : yield* view(reference.ino);
|
|
938
|
+
if (node?.kind !== "directory")
|
|
939
|
+
return yield* op.fail("ClosedCaller");
|
|
940
|
+
return node;
|
|
941
|
+
});
|
|
942
|
+
const directoryNow = Effect.fnUntraced(function* (ino) {
|
|
943
|
+
const node = yield* view(ino);
|
|
916
944
|
if (node?.kind !== "directory") {
|
|
917
945
|
return dieInvariant("Directory left the inode table during a transition");
|
|
918
946
|
}
|
|
919
947
|
return node;
|
|
920
|
-
};
|
|
921
|
-
const nodeNow = (ino)
|
|
922
|
-
const node = view(ino);
|
|
948
|
+
});
|
|
949
|
+
const nodeNow = Effect.fnUntraced(function* (ino) {
|
|
950
|
+
const node = yield* view(ino);
|
|
923
951
|
if (node === undefined)
|
|
924
952
|
return dieInvariant("Inode left the table during a transition");
|
|
925
953
|
return node;
|
|
926
|
-
};
|
|
954
|
+
});
|
|
927
955
|
const creationTimes = (times, now) => ({
|
|
928
956
|
atimeNs: times?.access.kind === "value" ? times.access.nanoseconds : now,
|
|
929
957
|
mtimeNs: times?.modification.kind === "value" ? times.modification.nanoseconds : now
|
|
930
958
|
});
|
|
931
|
-
const newDirectory = (parent, mode, now, times)
|
|
932
|
-
const
|
|
959
|
+
const newDirectory = Effect.fnUntraced(function* (parent, mode, now, times) {
|
|
960
|
+
const draft = yield* Draft;
|
|
961
|
+
const ino = yield* draft.allocate;
|
|
933
962
|
return {
|
|
934
963
|
kind: "directory",
|
|
935
964
|
ino,
|
|
@@ -942,9 +971,10 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
942
971
|
},
|
|
943
972
|
revision: 0n
|
|
944
973
|
};
|
|
945
|
-
};
|
|
946
|
-
const newFile = (parent, data, mode, now, owner, times)
|
|
947
|
-
const
|
|
974
|
+
});
|
|
975
|
+
const newFile = Effect.fnUntraced(function* (parent, data, mode, now, owner, times) {
|
|
976
|
+
const draft = yield* Draft;
|
|
977
|
+
const ino = yield* draft.allocate;
|
|
948
978
|
return {
|
|
949
979
|
kind: "file",
|
|
950
980
|
ino,
|
|
@@ -959,9 +989,10 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
959
989
|
},
|
|
960
990
|
revision: 0n
|
|
961
991
|
};
|
|
962
|
-
};
|
|
963
|
-
const newSymlink = (parent, target, now, times)
|
|
964
|
-
const
|
|
992
|
+
});
|
|
993
|
+
const newSymlink = Effect.fnUntraced(function* (parent, target, now, times) {
|
|
994
|
+
const draft = yield* Draft;
|
|
995
|
+
const ino = yield* draft.allocate;
|
|
965
996
|
return {
|
|
966
997
|
kind: "symlink",
|
|
967
998
|
ino,
|
|
@@ -976,14 +1007,14 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
976
1007
|
},
|
|
977
1008
|
revision: 0n
|
|
978
1009
|
};
|
|
979
|
-
};
|
|
1010
|
+
});
|
|
980
1011
|
const lookup = Effect.fnUntraced(function* (path, base, op, options = {}, referencedBase) {
|
|
981
1012
|
const pathOp = op.at(path.input);
|
|
982
1013
|
const { allowMissing = false, createMissing, followFinalSymlink = true, parentOnly = false } = options;
|
|
983
1014
|
if (reference.ino === undefined) {
|
|
984
1015
|
return yield* pathOp.fail("ClosedCaller");
|
|
985
1016
|
}
|
|
986
|
-
let current = path.absolute ? nodeNow(ROOT_INO) : yield* callerDirectory(pathOp);
|
|
1017
|
+
let current = path.absolute ? (yield* nodeNow(ROOT_INO)) : yield* callerDirectory(pathOp);
|
|
987
1018
|
if (!path.absolute && referencedBase !== undefined) {
|
|
988
1019
|
current = referencedBase;
|
|
989
1020
|
yield* authorize(current, identity, EXECUTE, op);
|
|
@@ -996,7 +1027,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
996
1027
|
if (target.volume !== volumeIdentity) {
|
|
997
1028
|
return yield* pathOp.fail("ForeignHandle");
|
|
998
1029
|
}
|
|
999
|
-
const directory = target.ino === undefined ? undefined : view(target.ino);
|
|
1030
|
+
const directory = target.ino === undefined ? undefined : (yield* view(target.ino));
|
|
1000
1031
|
if (directory?.kind !== "directory") {
|
|
1001
1032
|
return yield* pathOp.fail("InvalidHandle");
|
|
1002
1033
|
}
|
|
@@ -1024,7 +1055,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1024
1055
|
if (component === DOT_HEX)
|
|
1025
1056
|
continue;
|
|
1026
1057
|
if (component === DOT_DOT_HEX) {
|
|
1027
|
-
current = directoryNow(current.parent);
|
|
1058
|
+
current = yield* directoryNow(current.parent);
|
|
1028
1059
|
parent = undefined;
|
|
1029
1060
|
name = undefined;
|
|
1030
1061
|
continue;
|
|
@@ -1032,7 +1063,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1032
1063
|
parent = current;
|
|
1033
1064
|
name = component;
|
|
1034
1065
|
const childIno = current.entries.get(component);
|
|
1035
|
-
const child = childIno === undefined ? undefined : view(childIno);
|
|
1066
|
+
const child = childIno === undefined ? undefined : (yield* view(childIno));
|
|
1036
1067
|
if (child === undefined) {
|
|
1037
1068
|
if (allowMissing && index === work.components.length - 1) {
|
|
1038
1069
|
return { node: undefined, parent, name };
|
|
@@ -1069,7 +1100,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1069
1100
|
work = expanded.success;
|
|
1070
1101
|
linked = work.components.length - remaining + Math.max(0, linked - index - 1);
|
|
1071
1102
|
if (work.absolute)
|
|
1072
|
-
current = nodeNow(ROOT_INO);
|
|
1103
|
+
current = yield* nodeNow(ROOT_INO);
|
|
1073
1104
|
index = -1;
|
|
1074
1105
|
}
|
|
1075
1106
|
else
|
|
@@ -1137,7 +1168,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1137
1168
|
const resolving = resolve();
|
|
1138
1169
|
return coordinated(op, Effect.gen(function* () {
|
|
1139
1170
|
const resolved = yield* resolving;
|
|
1140
|
-
const node = nodeNow(resolved.ino);
|
|
1171
|
+
const node = yield* nodeNow(resolved.ino);
|
|
1141
1172
|
if (expected !== undefined && node.revision !== expected) {
|
|
1142
1173
|
return yield* resolved.op.fail("StaleReference", { field: "expected" });
|
|
1143
1174
|
}
|
|
@@ -1163,11 +1194,11 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1163
1194
|
const now = yield* timestamp(op);
|
|
1164
1195
|
if (size !== undefined && node.kind === "file")
|
|
1165
1196
|
yield* resizeAt(node, size, now, op);
|
|
1166
|
-
const sized = nodeNow(resolved.ino);
|
|
1197
|
+
const sized = yield* nodeNow(resolved.ino);
|
|
1167
1198
|
const gid = owner?.gid ?? sized.metadata.gid;
|
|
1168
1199
|
const cleared = chowned && sized.kind === "file" ? sized.metadata.mode & ~SET_ID_BITS : sized.metadata.mode;
|
|
1169
1200
|
if (chowned || mode !== undefined || timed) {
|
|
1170
|
-
|
|
1201
|
+
yield* (yield* Draft).put({
|
|
1171
1202
|
...sized,
|
|
1172
1203
|
metadata: {
|
|
1173
1204
|
...sized.metadata,
|
|
@@ -1180,7 +1211,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1180
1211
|
}
|
|
1181
1212
|
});
|
|
1182
1213
|
}
|
|
1183
|
-
publishNode(node.ino);
|
|
1214
|
+
yield* publishNode(node.ino);
|
|
1184
1215
|
}));
|
|
1185
1216
|
};
|
|
1186
1217
|
const authorizeRemoval = (parent, child, op) => (parent.metadata.mode & STICKY_BIT) !== 0 && !identity.privileged &&
|
|
@@ -1188,7 +1219,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1188
1219
|
? Effect.fail(op.fail("NotPermitted"))
|
|
1189
1220
|
: Effect.void;
|
|
1190
1221
|
const claimName = Effect.fnUntraced(function* (entry, trailingSlash = "allowed") {
|
|
1191
|
-
const parent = directoryNow(entry.parent);
|
|
1222
|
+
const parent = yield* directoryNow(entry.parent);
|
|
1192
1223
|
if (isDotComponent(entry.name)) {
|
|
1193
1224
|
return yield* entry.op.fail(entry.addressing === "entry" ? "InvalidArgument" : "AlreadyExists");
|
|
1194
1225
|
}
|
|
@@ -1203,17 +1234,18 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1203
1234
|
const makeDirectory = Effect.fnUntraced(function* (entry, request, op) {
|
|
1204
1235
|
const name = yield* claimName(entry);
|
|
1205
1236
|
yield* reserveEntry(entry.op);
|
|
1206
|
-
const parent = directoryNow(entry.parent);
|
|
1237
|
+
const parent = yield* directoryNow(entry.parent);
|
|
1207
1238
|
const before = parent.revision;
|
|
1208
1239
|
const now = yield* timestamp(op);
|
|
1209
1240
|
const mode = request.exactMode
|
|
1210
1241
|
? yield* permittedMode({ kind: "directory", uid: identity.uid, gid: parent.metadata.gid }, request.mode, op)
|
|
1211
1242
|
: (request.mode & 0o777 & ~umask) | (request.mode & STICKY_BIT);
|
|
1212
|
-
const child = newDirectory(parent, mode, now, request.times);
|
|
1213
|
-
attach(parent, name, child, now);
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1243
|
+
const child = yield* newDirectory(parent, mode, now, request.times);
|
|
1244
|
+
yield* attach(parent, name, child, now);
|
|
1245
|
+
const draft = yield* Draft;
|
|
1246
|
+
yield* draft.addEntries(1);
|
|
1247
|
+
yield* publishEntry("Create", parent.ino, name, child.ino);
|
|
1248
|
+
return { child: child.ino, directory: { before, after: draft.revision } };
|
|
1217
1249
|
});
|
|
1218
1250
|
// Creates every missing directory a path names in the running transition, so a failure anywhere discards
|
|
1219
1251
|
// them all. The mode applies to each directory created and the times to the one in the final position. An
|
|
@@ -1229,15 +1261,17 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1229
1261
|
const made = yield* Effect.result(makeDirectory(entry, request, op));
|
|
1230
1262
|
if (Result.isSuccess(made))
|
|
1231
1263
|
return made.success;
|
|
1232
|
-
const existingIno = entry.name === undefined
|
|
1233
|
-
|
|
1264
|
+
const existingIno = entry.name === undefined
|
|
1265
|
+
? undefined
|
|
1266
|
+
: (yield* directoryNow(entry.parent)).entries.get(entry.name);
|
|
1267
|
+
node = existingIno === undefined ? undefined : (yield* view(existingIno));
|
|
1234
1268
|
if (made.failure.code !== "AlreadyExists" || node?.kind !== "directory")
|
|
1235
1269
|
return yield* made.failure;
|
|
1236
1270
|
}
|
|
1237
1271
|
else {
|
|
1238
1272
|
entryOp = op.at(prepared.path.input);
|
|
1239
1273
|
const resolved = yield* lookup(prepared.path, prepared.base, op, {
|
|
1240
|
-
createMissing: (parent, name, final) => Effect.
|
|
1274
|
+
createMissing: (parent, name, final) => Effect.flatMap(makeDirectory({ parent: parent.ino, name, trailingSlash: false, addressing: "path", op: entryOp }, final ? request : { mode: request.mode, exactMode: request.exactMode }, op), (made) => {
|
|
1241
1275
|
if (!revisionsBefore.has(parent.ino))
|
|
1242
1276
|
revisionsBefore.set(parent.ino, made.directory.before);
|
|
1243
1277
|
return directoryNow(made.child);
|
|
@@ -1247,39 +1281,41 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1247
1281
|
}
|
|
1248
1282
|
if (node?.kind !== "directory")
|
|
1249
1283
|
return yield* entryOp.fail("AlreadyExists");
|
|
1250
|
-
const after = directoryNow(node.parent).revision;
|
|
1284
|
+
const after = (yield* directoryNow(node.parent)).revision;
|
|
1251
1285
|
return { child: node.ino, directory: { before: revisionsBefore.get(node.parent) ?? after, after } };
|
|
1252
1286
|
});
|
|
1253
1287
|
const linkNode = Effect.fnUntraced(function* (node, entry, op) {
|
|
1254
1288
|
const name = yield* claimName(entry, "rejected");
|
|
1255
1289
|
yield* reserveEntry(entry.op);
|
|
1256
|
-
const parent = directoryNow(entry.parent);
|
|
1290
|
+
const parent = yield* directoryNow(entry.parent);
|
|
1257
1291
|
const before = parent.revision;
|
|
1258
1292
|
const now = yield* timestamp(op);
|
|
1259
|
-
attach(parent, name, node, now);
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1293
|
+
yield* attach(parent, name, node, now);
|
|
1294
|
+
const draft = yield* Draft;
|
|
1295
|
+
yield* draft.addEntries(1);
|
|
1296
|
+
yield* publishEntry("Create", parent.ino, name, node.ino);
|
|
1297
|
+
return { before, after: draft.revision };
|
|
1263
1298
|
});
|
|
1264
1299
|
const makeSymlink = Effect.fnUntraced(function* (entry, target, times, op) {
|
|
1265
1300
|
const name = yield* claimName(entry, "rejected");
|
|
1266
1301
|
yield* reserveEntry(entry.op);
|
|
1267
1302
|
yield* reserveBytes(entry.op, BigInt(target.length));
|
|
1268
|
-
const parent = directoryNow(entry.parent);
|
|
1303
|
+
const parent = yield* directoryNow(entry.parent);
|
|
1269
1304
|
const before = parent.revision;
|
|
1270
1305
|
const now = yield* timestamp(op);
|
|
1271
|
-
const child = newSymlink(parent, target, now, times);
|
|
1272
|
-
attach(parent, name, child, now);
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1306
|
+
const child = yield* newSymlink(parent, target, now, times);
|
|
1307
|
+
yield* attach(parent, name, child, now);
|
|
1308
|
+
const draft = yield* Draft;
|
|
1309
|
+
yield* draft.addEntries(1);
|
|
1310
|
+
yield* draft.addUsedBytes(BigInt(target.length));
|
|
1311
|
+
yield* publishEntry("Create", parent.ino, name, child.ino);
|
|
1312
|
+
return { child: child.ino, directory: { before, after: draft.revision } };
|
|
1277
1313
|
});
|
|
1278
1314
|
const removeChild = Effect.fnUntraced(function* (parent, name, child, op) {
|
|
1279
1315
|
const before = parent.revision;
|
|
1280
1316
|
const now = yield* timestamp(op);
|
|
1281
|
-
const d =
|
|
1282
|
-
d.put(withEntries({
|
|
1317
|
+
const d = yield* Draft;
|
|
1318
|
+
yield* d.put(withEntries({
|
|
1283
1319
|
...parent,
|
|
1284
1320
|
metadata: {
|
|
1285
1321
|
...parent.metadata,
|
|
@@ -1288,22 +1324,22 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1288
1324
|
ctimeNs: now
|
|
1289
1325
|
}
|
|
1290
1326
|
}, (entries) => entries.delete(name)));
|
|
1291
|
-
publishEntry("Remove", parent.ino, name, child.ino);
|
|
1292
|
-
detach(child, parent.ino, name, now);
|
|
1293
|
-
d.
|
|
1327
|
+
yield* publishEntry("Remove", parent.ino, name, child.ino);
|
|
1328
|
+
yield* detach(child, parent.ino, name, now);
|
|
1329
|
+
yield* d.addEntries(-1);
|
|
1294
1330
|
return { before, after: d.revision };
|
|
1295
1331
|
});
|
|
1296
1332
|
// Authorizes removing from the entry's directory and returns the named child. Only a path can name a dot
|
|
1297
1333
|
// entry, and each verb reports it with its own code. Search permission comes before the lookup and write
|
|
1298
1334
|
// permission after it, as on Linux; `beforeWrite` holds the verb's own checks that Linux makes in between.
|
|
1299
1335
|
const removalTarget = Effect.fnUntraced(function* (entry, dotNameCode, beforeWrite) {
|
|
1300
|
-
const parent = directoryNow(entry.parent);
|
|
1336
|
+
const parent = yield* directoryNow(entry.parent);
|
|
1301
1337
|
if (isDotComponent(entry.name)) {
|
|
1302
1338
|
return yield* entry.op.fail(entry.addressing === "entry" ? "InvalidArgument" : dotNameCode);
|
|
1303
1339
|
}
|
|
1304
1340
|
yield* authorize(parent, identity, EXECUTE, entry.op);
|
|
1305
1341
|
const childIno = parent.entries.get(entry.name);
|
|
1306
|
-
const child = childIno === undefined ? undefined : view(childIno);
|
|
1342
|
+
const child = childIno === undefined ? undefined : (yield* view(childIno));
|
|
1307
1343
|
if (child === undefined)
|
|
1308
1344
|
return yield* entry.op.fail("NotFound");
|
|
1309
1345
|
const rejected = beforeWrite?.(child);
|
|
@@ -1338,8 +1374,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1338
1374
|
return yield* removeChild(parent, name, child, op);
|
|
1339
1375
|
});
|
|
1340
1376
|
const renameEntry = Effect.fnUntraced(function* (source, destination, op) {
|
|
1341
|
-
const sourceDirectory = directoryNow(source.parent);
|
|
1342
|
-
const destinationDirectory = directoryNow(destination.parent);
|
|
1377
|
+
const sourceDirectory = yield* directoryNow(source.parent);
|
|
1378
|
+
const destinationDirectory = yield* directoryNow(destination.parent);
|
|
1343
1379
|
const sameDirectory = source.parent === destination.parent;
|
|
1344
1380
|
const sourceName = source.name;
|
|
1345
1381
|
const destinationName = destination.name;
|
|
@@ -1353,11 +1389,11 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1353
1389
|
const sourceBefore = sourceDirectory.revision;
|
|
1354
1390
|
const destinationBefore = destinationDirectory.revision;
|
|
1355
1391
|
const childIno = sourceDirectory.entries.get(sourceName);
|
|
1356
|
-
const child = childIno === undefined ? undefined : view(childIno);
|
|
1392
|
+
const child = childIno === undefined ? undefined : (yield* view(childIno));
|
|
1357
1393
|
if (child === undefined)
|
|
1358
1394
|
return yield* source.op.fail("NotFound");
|
|
1359
1395
|
const replacedIno = destinationDirectory.entries.get(destinationName);
|
|
1360
|
-
const replaced = replacedIno === undefined ? undefined : view(replacedIno);
|
|
1396
|
+
const replaced = replacedIno === undefined ? undefined : (yield* view(replacedIno));
|
|
1361
1397
|
// A trailing slash on either side asks for a directory; a missing slashed destination is fine when the
|
|
1362
1398
|
// source is one, as Linux allows.
|
|
1363
1399
|
if ((source.trailingSlash || destination.trailingSlash) && child.kind !== "directory") {
|
|
@@ -1366,24 +1402,29 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1366
1402
|
if (destination.trailingSlash && replaced !== undefined && replaced.kind !== "directory") {
|
|
1367
1403
|
return yield* destination.op.fail("NotDirectory");
|
|
1368
1404
|
}
|
|
1369
|
-
const result = ()
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1405
|
+
const result = Effect.fnUntraced(function* () {
|
|
1406
|
+
return sameDirectory
|
|
1407
|
+
? {
|
|
1408
|
+
_tag: "SameDirectory",
|
|
1409
|
+
directory: { before: sourceBefore, after: (yield* directoryNow(source.parent)).revision }
|
|
1410
|
+
}
|
|
1411
|
+
: {
|
|
1412
|
+
_tag: "DifferentDirectories",
|
|
1413
|
+
sourceDirectory: { before: sourceBefore, after: (yield* directoryNow(source.parent)).revision },
|
|
1414
|
+
destinationDirectory: {
|
|
1415
|
+
before: destinationBefore,
|
|
1416
|
+
after: (yield* directoryNow(destination.parent)).revision
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
});
|
|
1420
|
+
for (let ancestor = destinationDirectory;; ancestor = yield* directoryNow(ancestor.parent)) {
|
|
1380
1421
|
if (ancestor.ino === child.ino)
|
|
1381
1422
|
return yield* destination.op.fail("InvalidArgument");
|
|
1382
1423
|
if (ancestor.ino === ROOT_INO)
|
|
1383
1424
|
break;
|
|
1384
1425
|
}
|
|
1385
1426
|
if (child.ino === replaced?.ino)
|
|
1386
|
-
return result();
|
|
1427
|
+
return yield* result();
|
|
1387
1428
|
yield* authorize(sourceDirectory, identity, WRITE, source.op);
|
|
1388
1429
|
yield* authorize(destinationDirectory, identity, WRITE, destination.op);
|
|
1389
1430
|
yield* authorizeRemoval(sourceDirectory, child, source.op);
|
|
@@ -1400,9 +1441,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1400
1441
|
}
|
|
1401
1442
|
}
|
|
1402
1443
|
const now = yield* timestamp(op);
|
|
1403
|
-
const d =
|
|
1404
|
-
publishEntry("Remove", sourceDirectory.ino, sourceName, child.ino);
|
|
1405
|
-
d.put(withEntries({
|
|
1444
|
+
const d = yield* Draft;
|
|
1445
|
+
yield* publishEntry("Remove", sourceDirectory.ino, sourceName, child.ino);
|
|
1446
|
+
yield* d.put(withEntries({
|
|
1406
1447
|
...sourceDirectory,
|
|
1407
1448
|
metadata: {
|
|
1408
1449
|
...sourceDirectory.metadata,
|
|
@@ -1411,8 +1452,8 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1411
1452
|
ctimeNs: now
|
|
1412
1453
|
}
|
|
1413
1454
|
}, (entries) => entries.delete(sourceName)));
|
|
1414
|
-
const destinationNow = directoryNow(destination.parent);
|
|
1415
|
-
d.put(withEntries({
|
|
1455
|
+
const destinationNow = yield* directoryNow(destination.parent);
|
|
1456
|
+
yield* d.put(withEntries({
|
|
1416
1457
|
...destinationNow,
|
|
1417
1458
|
metadata: {
|
|
1418
1459
|
...destinationNow.metadata,
|
|
@@ -1421,9 +1462,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1421
1462
|
ctimeNs: now
|
|
1422
1463
|
}
|
|
1423
1464
|
}, (entries) => entries.set(destinationName, child.ino)));
|
|
1424
|
-
const moved = nodeNow(child.ino);
|
|
1465
|
+
const moved = yield* nodeNow(child.ino);
|
|
1425
1466
|
if (moved.kind === "directory") {
|
|
1426
|
-
d.put({
|
|
1467
|
+
yield* d.put({
|
|
1427
1468
|
...moved,
|
|
1428
1469
|
parent: destination.parent,
|
|
1429
1470
|
name: destinationName,
|
|
@@ -1431,7 +1472,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1431
1472
|
});
|
|
1432
1473
|
}
|
|
1433
1474
|
else {
|
|
1434
|
-
d.put({
|
|
1475
|
+
yield* d.put({
|
|
1435
1476
|
...moved,
|
|
1436
1477
|
links: [
|
|
1437
1478
|
...withoutLink(moved.links, source.parent, sourceName),
|
|
@@ -1441,11 +1482,11 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1441
1482
|
});
|
|
1442
1483
|
}
|
|
1443
1484
|
if (replaced !== undefined) {
|
|
1444
|
-
detach(nodeNow(replaced.ino), destination.parent, destinationName, now);
|
|
1445
|
-
d.
|
|
1485
|
+
yield* detach(yield* nodeNow(replaced.ino), destination.parent, destinationName, now);
|
|
1486
|
+
yield* d.addEntries(-1);
|
|
1446
1487
|
}
|
|
1447
|
-
publishEntry("Create", destination.parent, destinationName, child.ino);
|
|
1448
|
-
return result();
|
|
1488
|
+
yield* publishEntry("Create", destination.parent, destinationName, child.ino);
|
|
1489
|
+
return yield* result();
|
|
1449
1490
|
});
|
|
1450
1491
|
const openExisting = Effect.fnUntraced(function* (file, request, at, op) {
|
|
1451
1492
|
yield* authorize(file, identity, request.access === "read" ? READ : request.access === "write" ? WRITE : READ | WRITE, at);
|
|
@@ -1454,7 +1495,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1454
1495
|
});
|
|
1455
1496
|
const openFile = Effect.fnUntraced(function* (entry, found, request, acquired, op) {
|
|
1456
1497
|
const { name } = entry;
|
|
1457
|
-
const parent = directoryNow(entry.parent);
|
|
1498
|
+
const parent = yield* directoryNow(entry.parent);
|
|
1458
1499
|
const before = parent.revision;
|
|
1459
1500
|
let file = found;
|
|
1460
1501
|
let created = false;
|
|
@@ -1481,12 +1522,13 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1481
1522
|
const mode = request.exactMode
|
|
1482
1523
|
? yield* permittedMode({ kind: "file", uid: owner?.uid ?? identity.uid, gid: owner?.gid ?? parent.metadata.gid }, request.mode, op)
|
|
1483
1524
|
: (request.mode ?? 0o666) & 0o777 & ~umask;
|
|
1484
|
-
file = newFile(parent, new Uint8Array(Number(size)), mode, now, owner, request.times);
|
|
1485
|
-
attach(parent, name, file, now);
|
|
1486
|
-
|
|
1487
|
-
|
|
1525
|
+
file = yield* newFile(parent, new Uint8Array(Number(size)), mode, now, owner, request.times);
|
|
1526
|
+
yield* attach(parent, name, file, now);
|
|
1527
|
+
const draft = yield* Draft;
|
|
1528
|
+
yield* draft.addEntries(1);
|
|
1529
|
+
yield* draft.addUsedBytes(size);
|
|
1488
1530
|
created = true;
|
|
1489
|
-
publishEntry("Create", parent.ino, name, file.ino);
|
|
1531
|
+
yield* publishEntry("Create", parent.ino, name, file.ino);
|
|
1490
1532
|
}
|
|
1491
1533
|
else {
|
|
1492
1534
|
if (file.kind === "symlink")
|
|
@@ -1495,17 +1537,18 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1495
1537
|
return yield* entry.op.fail("IsDirectory");
|
|
1496
1538
|
yield* openExisting(file, request, entry.op, op);
|
|
1497
1539
|
}
|
|
1498
|
-
|
|
1499
|
-
bindFile(acquired, file.ino);
|
|
1500
|
-
return { ino: file.ino, created, directory: { before, after: directoryNow(entry.parent).revision } };
|
|
1540
|
+
yield* (yield* Draft).retain(file.ino);
|
|
1541
|
+
yield* bindFile(acquired, file.ino);
|
|
1542
|
+
return { ino: file.ino, created, directory: { before, after: (yield* directoryNow(entry.parent)).revision } };
|
|
1501
1543
|
});
|
|
1502
|
-
const releasePending = (opened) => () => {
|
|
1544
|
+
const releasePending = (opened) => () => Effect.gen(function* () {
|
|
1503
1545
|
const ino = opened();
|
|
1504
1546
|
if (ino === undefined)
|
|
1505
1547
|
return;
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1548
|
+
const draft = yield* Draft;
|
|
1549
|
+
yield* draft.release(ino);
|
|
1550
|
+
yield* reclaim(draft, ino);
|
|
1551
|
+
});
|
|
1509
1552
|
const acquireOpenedFile = (ref, op, acquire) => {
|
|
1510
1553
|
let opened;
|
|
1511
1554
|
return acquireHandle(ref, (effect) => coordinated(op, effect), acquire((ino) => {
|
|
@@ -1521,7 +1564,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1521
1564
|
return yield* op.fail("InvalidHandle");
|
|
1522
1565
|
if (ref.volume !== volumeIdentity)
|
|
1523
1566
|
return yield* op.fail("ForeignHandle");
|
|
1524
|
-
const node = ref.ino === undefined ? undefined : view(ref.ino);
|
|
1567
|
+
const node = ref.ino === undefined ? undefined : (yield* view(ref.ino));
|
|
1525
1568
|
if (node === undefined)
|
|
1526
1569
|
return yield* op.fail("InvalidHandle");
|
|
1527
1570
|
return node;
|
|
@@ -1561,7 +1604,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1561
1604
|
if (!isAttachedBytes(bytes) || bytes.length === 0 || bytes.length > MAX_NAME_BYTES || bytes.includes(0) ||
|
|
1562
1605
|
bytes.includes(SLASH_BYTE))
|
|
1563
1606
|
return Result.fail(op.fail("InvalidArgument"));
|
|
1564
|
-
return Result.succeed(
|
|
1607
|
+
return Result.succeed(Hex.encode(new Uint8Array(bytes)));
|
|
1565
1608
|
};
|
|
1566
1609
|
const prepareEntry = (input, op) => isEntry(input)
|
|
1567
1610
|
? Result.map(entryName(input.name, op), (name) => ({ kind: "entry", directory: input.directory, name }))
|
|
@@ -1594,7 +1637,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1594
1637
|
}),
|
|
1595
1638
|
// Nothing to undo here: the finalizer that follows an interrupted acquisition releases the hold under
|
|
1596
1639
|
// every permit, where a detached directory may leave the table.
|
|
1597
|
-
() =>
|
|
1640
|
+
() => Effect.void, finalizeDirectory(acquired));
|
|
1598
1641
|
});
|
|
1599
1642
|
const fail = (op, cause, field) => op.fail("InvalidArgument", field === undefined ? { cause } : { cause, field });
|
|
1600
1643
|
const preparedOp = (prepared, op) => prepared.kind === "path" ? op.at(prepared.path.input) : op;
|
|
@@ -1656,8 +1699,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1656
1699
|
if (node.metadata.nlink === 0)
|
|
1657
1700
|
return yield* resolved.op.fail("StaleReference");
|
|
1658
1701
|
yield* openExisting(node, chosen, resolved.op, op);
|
|
1659
|
-
|
|
1660
|
-
|
|
1702
|
+
const draft = yield* Draft;
|
|
1703
|
+
yield* draft.retain(node.ino);
|
|
1704
|
+
yield* bindFile(acquired, node.ino);
|
|
1661
1705
|
opened(node.ino);
|
|
1662
1706
|
return fileHandle(acquired);
|
|
1663
1707
|
}));
|
|
@@ -1684,7 +1728,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1684
1728
|
return yield* op.fail("InvalidArgument");
|
|
1685
1729
|
yield* authorize(parent, identity, EXECUTE, op);
|
|
1686
1730
|
const directIno = parent.entries.get(name);
|
|
1687
|
-
const direct = directIno === undefined ? undefined : view(directIno);
|
|
1731
|
+
const direct = directIno === undefined ? undefined : (yield* view(directIno));
|
|
1688
1732
|
if (expected !== undefined) {
|
|
1689
1733
|
let expectedIno;
|
|
1690
1734
|
if (expected !== null) {
|
|
@@ -1739,29 +1783,31 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1739
1783
|
};
|
|
1740
1784
|
}));
|
|
1741
1785
|
});
|
|
1742
|
-
const walkChildren = (directory, path, depth, up)
|
|
1786
|
+
const walkChildren = Effect.fnUntraced(function* (directory, path, depth, up) {
|
|
1743
1787
|
const listed = referenceFor(directory.ino);
|
|
1744
|
-
|
|
1745
|
-
|
|
1788
|
+
const frames = [];
|
|
1789
|
+
for (const [name, childIno] of Arr.sort(directory.entries, byEntryName)) {
|
|
1790
|
+
const child = yield* view(childIno);
|
|
1746
1791
|
if (child === undefined)
|
|
1747
|
-
|
|
1792
|
+
continue;
|
|
1748
1793
|
const bytes = nameBytes(name);
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1794
|
+
frames.push({
|
|
1795
|
+
ino: child.ino,
|
|
1796
|
+
kind: child.kind,
|
|
1797
|
+
name: bytes,
|
|
1798
|
+
key: name,
|
|
1799
|
+
path: joinPath(path, bytes),
|
|
1800
|
+
parent: directory.ino,
|
|
1801
|
+
up,
|
|
1802
|
+
depth: depth + 1,
|
|
1803
|
+
bytes: child.kind === "directory" ? 0n : child.metadata.size,
|
|
1804
|
+
reference: referenceFor(child.ino),
|
|
1805
|
+
directory: listed,
|
|
1806
|
+
listed: false
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
return frames;
|
|
1810
|
+
});
|
|
1765
1811
|
const anchorFrame = (parent, key, root) => ({
|
|
1766
1812
|
ino: root.ino,
|
|
1767
1813
|
kind: root.kind,
|
|
@@ -1782,13 +1828,12 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1782
1828
|
const prepared = prepare(target.path, op);
|
|
1783
1829
|
if (Result.isFailure(prepared))
|
|
1784
1830
|
return Effect.undefined;
|
|
1785
|
-
return ResolvedEntry.fromPath(prepared.success, target.relativeTo, op).pipe(Effect.map((entry) => {
|
|
1786
|
-
const parent = view(entry.parent);
|
|
1831
|
+
return ResolvedEntry.fromPath(prepared.success, target.relativeTo, op).pipe(Effect.flatMap((entry) => Effect.map(view(entry.parent), (parent) => {
|
|
1787
1832
|
return entry.name !== undefined && parent?.kind === "directory" &&
|
|
1788
1833
|
parent.entries.get(entry.name) === root.ino
|
|
1789
1834
|
? anchorFrame(parent, entry.name, root)
|
|
1790
1835
|
: undefined;
|
|
1791
|
-
}), Effect.catch(() => Effect.undefined));
|
|
1836
|
+
})), Effect.catch(() => Effect.undefined));
|
|
1792
1837
|
};
|
|
1793
1838
|
const holds = (parent, frame) => parent?.kind === "directory" && parent.metadata.nlink > 0 && parent.entries.get(frame.key) === frame.ino;
|
|
1794
1839
|
// The node a frame names, reached by name from the walk's root as a path lookup reaches it: every directory on
|
|
@@ -1801,13 +1846,13 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1801
1846
|
const chain = [];
|
|
1802
1847
|
for (let link = frame; link !== undefined; link = link.up)
|
|
1803
1848
|
chain.push(link);
|
|
1804
|
-
let node = view((chain.at(-1) ?? frame).parent);
|
|
1849
|
+
let node = yield* view((chain.at(-1) ?? frame).parent);
|
|
1805
1850
|
for (let index = chain.length - 1; index >= 0; index--) {
|
|
1806
1851
|
const link = chain[index];
|
|
1807
1852
|
if (link === undefined || node?.kind !== "directory" || node.metadata.nlink === 0)
|
|
1808
1853
|
return undefined;
|
|
1809
1854
|
yield* authorize(node, identity, EXECUTE, at);
|
|
1810
|
-
node = node.entries.get(link.key) === link.ino ? view(link.ino) : undefined;
|
|
1855
|
+
node = node.entries.get(link.key) === link.ino ? (yield* view(link.ino)) : undefined;
|
|
1811
1856
|
}
|
|
1812
1857
|
return node;
|
|
1813
1858
|
});
|
|
@@ -1828,7 +1873,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1828
1873
|
const at = op.at(locate(frame.path));
|
|
1829
1874
|
return Effect.flatMap(reachFrame(frame, at), (node) => node?.kind !== "directory" || node.metadata.nlink === 0
|
|
1830
1875
|
? Effect.succeed([])
|
|
1831
|
-
: Effect.
|
|
1876
|
+
: Effect.andThen(listable(node, at), walkChildren(node, frame.path, frame.depth, frame)));
|
|
1832
1877
|
}));
|
|
1833
1878
|
const exceeded = (frame, field) => makeError({ code: "LimitExceeded", operation: op.operation, field, path: errorPath(locate(frame.path)) });
|
|
1834
1879
|
const admit = (frame) => {
|
|
@@ -1900,10 +1945,10 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1900
1945
|
let targetAt = preparedOp(prepared, op);
|
|
1901
1946
|
const first = coordinatedRead(op, Effect.gen(function* () {
|
|
1902
1947
|
const entry = yield* resolveEntry(prepared, op);
|
|
1903
|
-
const parent = directoryNow(entry.parent);
|
|
1948
|
+
const parent = yield* directoryNow(entry.parent);
|
|
1904
1949
|
yield* authorize(parent, identity, EXECUTE, entry.op);
|
|
1905
1950
|
const childIno = entry.name === undefined ? undefined : parent.entries.get(entry.name);
|
|
1906
|
-
const child = childIno === undefined ? undefined : view(childIno);
|
|
1951
|
+
const child = childIno === undefined ? undefined : (yield* view(childIno));
|
|
1907
1952
|
if (entry.name === undefined || child === undefined)
|
|
1908
1953
|
return yield* entry.op.fail("NotFound");
|
|
1909
1954
|
if (child.kind !== "directory")
|
|
@@ -1911,16 +1956,16 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1911
1956
|
yield* listable(child, entry.op);
|
|
1912
1957
|
anchor = anchorFrame(parent, entry.name, child);
|
|
1913
1958
|
targetAt = entry.op;
|
|
1914
|
-
return walkChildren(child, new Uint8Array(0), 0, anchor);
|
|
1959
|
+
return yield* walkChildren(child, new Uint8Array(0), 0, anchor);
|
|
1915
1960
|
})).pipe(Effect.catchIf((error) => force && error.code === "NotFound", () => Effect.succeed([])));
|
|
1916
1961
|
const seams = yield* VolumeTestSeams;
|
|
1917
1962
|
const removeFrame = (frame) => {
|
|
1918
1963
|
const at = op.at(locate(frame.path));
|
|
1919
|
-
return Effect.andThen(seams.beforeTreeRemoval, coordinated(op, Effect.
|
|
1920
|
-
if (anchor !== undefined && !holds(view(anchor.parent), anchor)) {
|
|
1921
|
-
return force ? Effect.void : Effect.fail(targetAt.fail("NotFound"));
|
|
1964
|
+
return Effect.andThen(seams.beforeTreeRemoval, coordinated(op, Effect.gen(function* () {
|
|
1965
|
+
if (anchor !== undefined && !holds(yield* view(anchor.parent), anchor)) {
|
|
1966
|
+
return yield* (force ? Effect.void : Effect.fail(targetAt.fail("NotFound")));
|
|
1922
1967
|
}
|
|
1923
|
-
return Effect.flatMap(frame.up === undefined ?
|
|
1968
|
+
return yield* Effect.flatMap(frame.up === undefined ? view(frame.parent) : reachFrame(frame.up, at), (parent) => !holds(parent, frame)
|
|
1924
1969
|
? Effect.fail(at.fail("NotFound"))
|
|
1925
1970
|
: removeEntry({
|
|
1926
1971
|
parent: parent.ino,
|
|
@@ -1950,7 +1995,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1950
1995
|
const prepared = yield* Effect.fromResult(prepareEntry(input, op));
|
|
1951
1996
|
return yield* coordinatedRead(op, Effect.gen(function* () {
|
|
1952
1997
|
const entry = yield* resolveEntry(prepared, op);
|
|
1953
|
-
const directory = directoryNow(entry.parent);
|
|
1998
|
+
const directory = yield* directoryNow(entry.parent);
|
|
1954
1999
|
if (isDotComponent(entry.name)) {
|
|
1955
2000
|
return yield* entry.op.fail(entry.addressing === "entry" ? "InvalidArgument" : "NotFound");
|
|
1956
2001
|
}
|
|
@@ -1997,7 +2042,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1997
2042
|
const first = coordinatedRead(op, Effect.gen(function* () {
|
|
1998
2043
|
const directory = yield* resolveDirectory(target, op);
|
|
1999
2044
|
yield* authorize(directory.node, identity, READ, directory.op);
|
|
2000
|
-
return walkChildren(directory.node, new Uint8Array(0), 0, yield* pathAnchor(target, directory.node, op));
|
|
2045
|
+
return yield* walkChildren(directory.node, new Uint8Array(0), 0, yield* pathAnchor(target, directory.node, op));
|
|
2001
2046
|
}));
|
|
2002
2047
|
const plan = {
|
|
2003
2048
|
order: chosen.order ?? "pre",
|
|
@@ -2033,7 +2078,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
2033
2078
|
const pathOp = op.at(target.path);
|
|
2034
2079
|
const result = yield* lookup(yield* Effect.fromResult(prepare(target.path, op)), target.relativeTo, op);
|
|
2035
2080
|
const directory = result.node?.kind === "directory" ? result.node : result.parent;
|
|
2036
|
-
const prefix = directory === undefined ? SLASH_HEX :
|
|
2081
|
+
const prefix = directory === undefined ? SLASH_HEX : yield* pathOfCurrent(directory.ino);
|
|
2037
2082
|
if (prefix === undefined)
|
|
2038
2083
|
return yield* pathOp.fail("NotFound");
|
|
2039
2084
|
if (result.node?.kind === "directory" || result.name === undefined)
|
|
@@ -2041,7 +2086,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
2041
2086
|
return ownedPath(nameBytes(prefix + (prefix === SLASH_HEX ? "" : SLASH_HEX) + result.name));
|
|
2042
2087
|
}
|
|
2043
2088
|
const resolved = yield* resolveTarget(target, op);
|
|
2044
|
-
const path =
|
|
2089
|
+
const path = yield* pathOfCurrent(resolved.node.ino);
|
|
2045
2090
|
if (path === undefined)
|
|
2046
2091
|
return yield* resolved.op.fail("NotFound");
|
|
2047
2092
|
return ownedPath(nameBytes(path));
|
|
@@ -2126,7 +2171,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
2126
2171
|
parent = directory.node;
|
|
2127
2172
|
name = prepared.name;
|
|
2128
2173
|
const childIno = parent.entries.get(name);
|
|
2129
|
-
let child = childIno === undefined ? undefined : view(childIno);
|
|
2174
|
+
let child = childIno === undefined ? undefined : (yield* view(childIno));
|
|
2130
2175
|
if (child?.kind === "symlink" && chosen.replaceFinalSymlink !== true &&
|
|
2131
2176
|
chosen.followFinalSymlink !== false) {
|
|
2132
2177
|
const resolved = yield* lookup(yield* Effect.fromResult(preparePath(ownedPath(nameBytes(name)), op.operation, undefined)), undefined, op, {
|
|
@@ -2161,7 +2206,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
2161
2206
|
yield* authorize(parent, identity, WRITE | EXECUTE, entryOp);
|
|
2162
2207
|
if (replaced !== undefined)
|
|
2163
2208
|
yield* authorizeRemoval(parent, replaced, entryOp);
|
|
2164
|
-
yield* replaced === undefined ? reserveEntry(entryOp) : reserveInode(entryOp);
|
|
2209
|
+
yield* (replaced === undefined ? reserveEntry(entryOp) : reserveInode(entryOp));
|
|
2165
2210
|
}
|
|
2166
2211
|
else {
|
|
2167
2212
|
yield* authorize(file, identity, chosen.access === "readWrite" ? READ | WRITE : WRITE, entryOp);
|
|
@@ -2186,8 +2231,9 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
2186
2231
|
data.set(captured, position);
|
|
2187
2232
|
}
|
|
2188
2233
|
const now = yield* timestamp(op);
|
|
2189
|
-
const d =
|
|
2190
|
-
const node = file ??
|
|
2234
|
+
const d = yield* Draft;
|
|
2235
|
+
const node = file ??
|
|
2236
|
+
(yield* newFile(parent, new Uint8Array(0), (chosen.mode ?? 0o666) & 0o777 & ~umask, now));
|
|
2191
2237
|
const written = {
|
|
2192
2238
|
...node,
|
|
2193
2239
|
data,
|
|
@@ -2199,18 +2245,18 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
2199
2245
|
ctimeNs: now
|
|
2200
2246
|
}
|
|
2201
2247
|
};
|
|
2202
|
-
d.
|
|
2248
|
+
yield* d.addUsedBytes(BigInt(size - previous));
|
|
2203
2249
|
if (file === undefined) {
|
|
2204
2250
|
if (replaced !== undefined)
|
|
2205
|
-
detach(replaced, parent.ino, name, now);
|
|
2206
|
-
attach(directoryNow(parent.ino), name, written, now);
|
|
2251
|
+
yield* detach(replaced, parent.ino, name, now);
|
|
2252
|
+
yield* attach(yield* directoryNow(parent.ino), name, written, now);
|
|
2207
2253
|
if (replaced === undefined)
|
|
2208
|
-
d.
|
|
2209
|
-
publishEntry(replaced === undefined ? "Create" : "Update", parent.ino, name, written.ino);
|
|
2254
|
+
yield* d.addEntries(1);
|
|
2255
|
+
yield* publishEntry(replaced === undefined ? "Create" : "Update", parent.ino, name, written.ino);
|
|
2210
2256
|
}
|
|
2211
2257
|
else {
|
|
2212
|
-
d.put(written);
|
|
2213
|
-
publishNode(written.ino);
|
|
2258
|
+
yield* d.put(written);
|
|
2259
|
+
yield* publishNode(written.ino);
|
|
2214
2260
|
}
|
|
2215
2261
|
}));
|
|
2216
2262
|
}),
|
|
@@ -2371,18 +2417,17 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
2371
2417
|
const statOp = OpContext.make("stat");
|
|
2372
2418
|
const handle = Object.freeze({
|
|
2373
2419
|
[DirectoryHandleId]: true,
|
|
2374
|
-
stat: coordinatedRead(statOp, Effect.
|
|
2375
|
-
const node = acquired.ino === undefined ? undefined : view(acquired.ino);
|
|
2376
|
-
return node === undefined
|
|
2420
|
+
stat: coordinatedRead(statOp, Effect.gen(function* () {
|
|
2421
|
+
const node = acquired.ino === undefined ? undefined : yield* view(acquired.ino);
|
|
2422
|
+
return yield* (node === undefined
|
|
2377
2423
|
? Effect.fail(statOp.fail("InvalidHandle"))
|
|
2378
|
-
: Effect.succeed(withMetadata(node));
|
|
2424
|
+
: Effect.succeed(withMetadata(node)));
|
|
2379
2425
|
})).pipe(Effect.withSpan("DirectoryHandle.stat")),
|
|
2380
2426
|
close: coordinatedCleanup(Effect.suspend(() => {
|
|
2381
2427
|
if (acquired.ino === undefined) {
|
|
2382
2428
|
return Effect.fail(OpContext.make("close").fail("InvalidHandle"));
|
|
2383
2429
|
}
|
|
2384
|
-
releaseDirectory(acquired);
|
|
2385
|
-
return Effect.void;
|
|
2430
|
+
return releaseDirectory(acquired);
|
|
2386
2431
|
})).pipe(Effect.ensuring(closeReleasedScope(acquired)), Effect.withSpan("DirectoryHandle.close"))
|
|
2387
2432
|
});
|
|
2388
2433
|
handles.set(handle, acquired);
|