@openclaw/openshell-sandbox 2026.10.1-beta.2 → 2026.10.5-beta.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/index.js +69 -111
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +91 -0
- package/node_modules/@openclaw/fs-safe/README.md +1 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-entry.js +7 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +5 -4
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +22 -38
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +117 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +38 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +195 -0
- package/node_modules/@openclaw/fs-safe/dist/create.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/create.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/errors.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-acquire.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-acquire.js +57 -11
- package/node_modules/@openclaw/fs-safe/dist/guarded-mutation.js +5 -5
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +52 -5
- package/node_modules/@openclaw/fs-safe/dist/guest.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +54 -0
- package/node_modules/@openclaw/fs-safe/dist/native-noreplace.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/native-noreplace.js +55 -0
- package/node_modules/@openclaw/fs-safe/dist/native-operations.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/native-operations.js +22 -26
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +12 -2
- package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +52 -6
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +2 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +17 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +6 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +35 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +6 -5
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +6 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-input.d.ts +15 -1
- package/node_modules/@openclaw/fs-safe/dist/root-create-input.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +6 -12
- package/node_modules/@openclaw/fs-safe/dist/root-errors.js +25 -4
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +18 -18
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +4 -2
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +13 -17
- package/node_modules/@openclaw/fs-safe/dist/root-paths.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/root-write-verification.d.ts +10 -1
- package/node_modules/@openclaw/fs-safe/dist/root-write-verification.js +20 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +19 -7
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +18 -3
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +25 -3
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +187 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +15 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-directory-diagnostics.d.ts +10 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-directory-diagnostics.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-target.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +6 -6
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +4 -4
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +17 -16
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +20 -28
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +27 -4
- package/node_modules/@openclaw/fs-safe/dist/walk.js +2 -13
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +3 -9
- package/node_modules/@openclaw/fs-safe/dist/watch.js +2 -3
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +1 -0
- package/node_modules/@openclaw/fs-safe/docs/archive.md +5 -1
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +11 -3
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/copy.md +109 -1
- package/node_modules/@openclaw/fs-safe/docs/durability.md +50 -5
- package/node_modules/@openclaw/fs-safe/docs/errors.md +37 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +21 -4
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +5 -3
- package/node_modules/@openclaw/fs-safe/docs/native.md +39 -6
- package/node_modules/@openclaw/fs-safe/docs/path-scope.md +4 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +3 -1
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +46 -8
- package/node_modules/@openclaw/fs-safe/docs/staged-file.md +10 -3
- package/node_modules/@openclaw/fs-safe/docs/temp.md +12 -0
- package/node_modules/@openclaw/fs-safe/docs/watch.md +6 -3
- package/node_modules/@openclaw/fs-safe/docs/writing.md +40 -11
- package/node_modules/@openclaw/fs-safe/package.json +16 -16
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/package.json +5 -5
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-arbitration.d.ts +0 -17
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-arbitration.js +0 -66
- package/node_modules/@openclaw/fs-safe/dist/root-move-preflight.d.ts +0 -7
- package/node_modules/@openclaw/fs-safe/dist/root-move-preflight.js +0 -16
|
@@ -13,6 +13,9 @@ export type PublishFileExclusiveFailureDetails = {
|
|
|
13
13
|
targetIdentity?: FileIdentityStat;
|
|
14
14
|
cleanup: PublishFileExclusiveCleanup;
|
|
15
15
|
directorySync?: PublishFileExclusiveDirectorySyncFailure;
|
|
16
|
+
fallback?: "link-unlink";
|
|
17
|
+
publication?: "published";
|
|
18
|
+
sourceRemoval?: "not-attempted" | "still-linked" | "unverified" | "removed";
|
|
16
19
|
};
|
|
17
20
|
export type PublishFailureState = {
|
|
18
21
|
phase: PublishFileExclusiveFailurePhase;
|
|
@@ -21,6 +24,8 @@ export type PublishFailureState = {
|
|
|
21
24
|
targetCleanupIdentity?: FileIdentityStat;
|
|
22
25
|
preserveTarget: boolean;
|
|
23
26
|
directorySync?: PublishFileExclusiveDirectorySyncFailure;
|
|
27
|
+
fallback?: PublishFileExclusiveFailureDetails["fallback"];
|
|
28
|
+
sourceRemoval?: PublishFileExclusiveFailureDetails["sourceRemoval"];
|
|
24
29
|
};
|
|
25
30
|
export declare function rememberCreatedTarget(state: PublishFailureState, identity: FileIdentityStat, phase: PublishFileExclusiveFailurePhase): void;
|
|
26
31
|
export declare function publicationFailure(error: unknown, state: PublishFailureState, cleanup: PublishFileExclusiveCleanup): FsSafeError;
|
|
@@ -12,6 +12,7 @@ export function publicationFailure(error, state, cleanup) {
|
|
|
12
12
|
targetCreated: state.targetCreated,
|
|
13
13
|
...(state.targetIdentity ? { targetIdentity: state.targetIdentity } : {}),
|
|
14
14
|
...(state.directorySync ? { directorySync: state.directorySync } : {}),
|
|
15
|
+
...(state.fallback ? { fallback: state.fallback, publication: "published", sourceRemoval: state.sourceRemoval } : {}),
|
|
15
16
|
cleanup,
|
|
16
17
|
};
|
|
17
18
|
return new FsSafeError(error instanceof FsSafeError ? error.code : "helper-failed", `exclusive file publication failed during ${state.phase}: ${cause.message}`, { cause, details });
|
|
@@ -5,6 +5,8 @@ import { type PublishFileExclusiveSyncFailurePolicy } from "./publish-file-failu
|
|
|
5
5
|
export type PublishFileExclusiveStrategy = "link-or-copy" | "link-required" | "rename-noreplace";
|
|
6
6
|
export type PublishFileExclusiveResult = {
|
|
7
7
|
method: "hardlink" | "exclusive-copy" | "rename-noreplace";
|
|
8
|
+
/** Linux auto fallback; exclusive and identity-checked, but not crash-atomic. */
|
|
9
|
+
fallback?: "link-unlink";
|
|
8
10
|
identity: Stats;
|
|
9
11
|
directorySync: DirectorySyncOutcome;
|
|
10
12
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { nativeMoveNoReplace } from "./native-move-fallback.js";
|
|
2
|
+
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
import fsSync, {} from "node:fs";
|
|
3
5
|
import fs, {} from "node:fs/promises";
|
|
@@ -256,7 +258,7 @@ export async function publishFileExclusive(params) {
|
|
|
256
258
|
else
|
|
257
259
|
assertSourceCurrent();
|
|
258
260
|
assertPublishedTargetCurrent(targetPath, identity, "publication target changed during directory sync");
|
|
259
|
-
return { method, identity: fsSync.fstatSync(handle.fd), directorySync };
|
|
261
|
+
return { method, ...(failure.fallback ? { fallback: failure.fallback } : {}), identity: fsSync.fstatSync(handle.fd), directorySync };
|
|
260
262
|
};
|
|
261
263
|
await parent.assertCurrent();
|
|
262
264
|
assertSourceCurrent();
|
|
@@ -269,7 +271,38 @@ export async function publishFileExclusive(params) {
|
|
|
269
271
|
}
|
|
270
272
|
if (strategy === "rename-noreplace") {
|
|
271
273
|
const binding = requireNativeBinding();
|
|
272
|
-
|
|
274
|
+
try {
|
|
275
|
+
const fallback = nativeMoveNoReplace(binding, [
|
|
276
|
+
sourceNativeParent.handle.fd, sourceNativeParent.basename,
|
|
277
|
+
targetNativeParent.handle.fd, targetNativeParent.basename,
|
|
278
|
+
], sourceExactIdentity, getFsSafeNativeConfig().mode === "auto", "file publication");
|
|
279
|
+
if (fallback) {
|
|
280
|
+
failure.fallback = fallback;
|
|
281
|
+
failure.sourceRemoval = "removed";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (error instanceof FsSafeError) {
|
|
286
|
+
if (error.details?.publication === "published") {
|
|
287
|
+
failure.targetCreated = true;
|
|
288
|
+
failure.preserveTarget = true;
|
|
289
|
+
failure.phase = "rename-verify";
|
|
290
|
+
failure.fallback = "link-unlink";
|
|
291
|
+
failure.sourceRemoval = error.details.sourceRemoval;
|
|
292
|
+
// Changed/unverified pairs cannot establish which inode was published.
|
|
293
|
+
if (failure.sourceRemoval === "still-linked")
|
|
294
|
+
rememberCreatedTarget(failure, sourceExactIdentity, "rename-verify");
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
const code = error?.code;
|
|
299
|
+
if (code === "path-mismatch" || code === "hardlink") {
|
|
300
|
+
throw new FsSafeError(code, "publication source changed before fallback", { cause: error });
|
|
301
|
+
}
|
|
302
|
+
if (code === "ELOOP")
|
|
303
|
+
throw new FsSafeError("symlink", "publication source became a symlink", { cause: error });
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
273
306
|
rememberCreatedTarget(failure, sourceExactIdentity, "rename-verify");
|
|
274
307
|
// A failed post-rename fence must not delete the only remaining name.
|
|
275
308
|
failure.preserveTarget = true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { noReplaceUnavailable } from "./native-noreplace.js";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import fsSync from "node:fs";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
@@ -19,7 +20,8 @@ function assertGuardsCurrent(guards) {
|
|
|
19
20
|
}
|
|
20
21
|
function replacementFailure(error, message, details, cause = error) {
|
|
21
22
|
if (error instanceof FsSafeError) {
|
|
22
|
-
|
|
23
|
+
const code = error.code;
|
|
24
|
+
return new FsSafeError(code, code === "helper-unavailable" ? `${message}: ${error.message}` : message, { cause, details });
|
|
23
25
|
}
|
|
24
26
|
const failure = new Error(message, { cause });
|
|
25
27
|
copyOperationalCode(failure, error);
|
|
@@ -46,14 +48,13 @@ function observeOptionalDirectory(pathname) {
|
|
|
46
48
|
return inspectDirectoryIdentitySync(pathname, undefined, initial);
|
|
47
49
|
}
|
|
48
50
|
function normalizeNativeRenameFailure(error) {
|
|
51
|
+
const unavailable = noReplaceUnavailable(error, "directory rename", true);
|
|
52
|
+
if (unavailable)
|
|
53
|
+
return unavailable;
|
|
49
54
|
const code = error?.code;
|
|
50
55
|
if (code === NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH) {
|
|
51
56
|
return new FsSafeError("path-mismatch", "directory replacement source identity changed before native rename", { cause: error });
|
|
52
57
|
}
|
|
53
|
-
if (code === "EINVAL" || code === "ENOSYS" || code === "ENOTSUP" ||
|
|
54
|
-
code === "EOPNOTSUPP") {
|
|
55
|
-
return new FsSafeError("helper-unavailable", "native no-replace directory rename is unavailable on this filesystem", { cause: error });
|
|
56
|
-
}
|
|
57
58
|
return error;
|
|
58
59
|
}
|
|
59
60
|
function renameRetainedDirectory(params) {
|
|
@@ -139,15 +139,12 @@ function* replacePinnedWithRestore(io, file, replacement, maxRestoreBytes, repla
|
|
|
139
139
|
if (io.asynchronous)
|
|
140
140
|
yield chmod;
|
|
141
141
|
yield* file.sync();
|
|
142
|
-
throw restoreFailure(writeError, "restored");
|
|
143
142
|
}
|
|
144
143
|
catch (restoreError) {
|
|
145
144
|
mutation.rethrowRefusal();
|
|
146
|
-
if (restoreError instanceof FsSafeError && restoreError.details?.cleanup === "restored") {
|
|
147
|
-
throw restoreError;
|
|
148
|
-
}
|
|
149
145
|
throw restoreFailure(writeError, "restore-failed", restoreError);
|
|
150
146
|
}
|
|
147
|
+
throw restoreFailure(writeError, "restored");
|
|
151
148
|
}
|
|
152
149
|
}
|
|
153
150
|
export function* copyFallbackReplace(io, params) {
|
|
@@ -139,6 +139,12 @@ export function* writeTempFile(io, params) {
|
|
|
139
139
|
const writing = file.writeFile(params.content, true);
|
|
140
140
|
if (io.asynchronous)
|
|
141
141
|
yield writing;
|
|
142
|
+
if (io.asyncFs === fs && process.platform !== "win32" && !params.sync) {
|
|
143
|
+
// Matching non-durable writes have no I/O left after this identity fence.
|
|
144
|
+
const current = inspectAtomicIdentity(io, () => file.statExact(), identity, true);
|
|
145
|
+
if (Number(current.mode & 4095n) === (params.mode & 0o7777))
|
|
146
|
+
return { file, identity };
|
|
147
|
+
}
|
|
142
148
|
const chmod = file.chmod(params.mode);
|
|
143
149
|
if (io.asynchronous)
|
|
144
150
|
yield chmod;
|
|
@@ -1,14 +1,28 @@
|
|
|
1
|
-
import type { PinnedWriteInput } from "./pinned-write-types.js";
|
|
1
|
+
import type { NativeWriteParent, PinnedWriteInput, PublishedWriteIdentity } from "./pinned-write-types.js";
|
|
2
2
|
import type { RootCreateOptions, RootCreateStreamOptions, RootWriteOptions } from "./root-options.js";
|
|
3
|
+
import type { AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
4
|
declare const exclusiveSidecarCreate: unique symbol;
|
|
4
5
|
export declare const sidecarExclusiveCreate: Readonly<{
|
|
5
6
|
[exclusiveSidecarCreate]: true;
|
|
6
7
|
}>;
|
|
8
|
+
export declare const retainSidecarCreated: unique symbol;
|
|
9
|
+
export type SidecarCreationRetention = Readonly<{
|
|
10
|
+
retain(fd: number, parent: AnyAsyncDirectoryGuard, nativeParent?: NativeWriteParent): void;
|
|
11
|
+
cleanup(fd: number, parent: NativeWriteParent, identity: PublishedWriteIdentity): void;
|
|
12
|
+
}>;
|
|
7
13
|
export type RootWriteParams = RootWriteOptions & RootCreateOptions & RootCreateStreamOptions & {
|
|
8
14
|
relativePath: string;
|
|
9
15
|
data: string | Buffer | AsyncIterable<Uint8Array>;
|
|
10
16
|
strictFileSync?: boolean;
|
|
11
17
|
[exclusiveSidecarCreate]?: true;
|
|
18
|
+
[retainSidecarCreated]?: SidecarCreationRetention;
|
|
19
|
+
};
|
|
20
|
+
export declare function sidecarRetainedCreate(retain: NonNullable<RootWriteParams[typeof retainSidecarCreated]>): {
|
|
21
|
+
[exclusiveSidecarCreate]: true;
|
|
22
|
+
[retainSidecarCreated]: Readonly<{
|
|
23
|
+
retain(fd: number, parent: AnyAsyncDirectoryGuard, nativeParent?: NativeWriteParent): void;
|
|
24
|
+
cleanup(fd: number, parent: NativeWriteParent, identity: PublishedWriteIdentity): void;
|
|
25
|
+
}>;
|
|
12
26
|
};
|
|
13
27
|
export declare function rethrowCreateInputError(error: unknown): never;
|
|
14
28
|
export declare function createInputOptions(data: RootWriteParams["data"], options: RootCreateOptions & RootCreateStreamOptions, defaultMaxBytes?: number): RootCreateOptions & RootCreateStreamOptions;
|
|
@@ -5,6 +5,10 @@ import { MutationAuthorityError } from "./mutation-authority.js";
|
|
|
5
5
|
// Keep this composition hook off the public Root options and package exports.
|
|
6
6
|
const exclusiveSidecarCreate = Symbol("exclusiveSidecarCreate");
|
|
7
7
|
export const sidecarExclusiveCreate = Object.freeze({ [exclusiveSidecarCreate]: true });
|
|
8
|
+
export const retainSidecarCreated = Symbol("retainSidecarCreated");
|
|
9
|
+
export function sidecarRetainedCreate(retain) {
|
|
10
|
+
return { ...sidecarExclusiveCreate, [retainSidecarCreated]: retain };
|
|
11
|
+
}
|
|
8
12
|
class CreateInputError extends FsSafeError {
|
|
9
13
|
rejection;
|
|
10
14
|
constructor(rejection) {
|
|
@@ -17,7 +17,7 @@ export async function lookupRootDirectoryEntry(root, guard, name) {
|
|
|
17
17
|
const observed = inspectStatObservationSync(bigint => bigint
|
|
18
18
|
? fs.lstatSync(pathname, { bigint: true }) : fs.lstatSync(pathname));
|
|
19
19
|
await assertRootDirectoryObservationGuard(root, guard);
|
|
20
|
-
return { entry:
|
|
20
|
+
return { entry: pathStatFromStats(observed.stat, name), identity: observed.identity };
|
|
21
21
|
}
|
|
22
22
|
catch (error) {
|
|
23
23
|
await assertRootDirectoryObservationGuard(root, guard);
|
|
@@ -16,6 +16,5 @@ export declare function assertRootDirectoryObservationGuard(root: RootContext, g
|
|
|
16
16
|
* both its identity and canonical check here.
|
|
17
17
|
*/
|
|
18
18
|
export declare function assertRootPathObservationReceiptCurrent(root: RootContext, receipt: RootPathObservationReceipt | RootPathParentObservationReceipt, finalTarget?: Stats | BigIntStats): void;
|
|
19
|
-
export declare function listDirectoryPath(root: RootContext, directory: string, withFileTypes: true, receipt?: RootPathObservationReceipt): Promise<DirEntry[]>;
|
|
20
19
|
export declare function listDirectoryPath(root: RootContext, directory: string, withFileTypes: boolean, receipt?: RootPathObservationReceipt): Promise<string[] | DirEntry[]>;
|
|
21
20
|
export declare function openRootDirectoryListing(root: RootContext, directory: string, options: RootDirectoryListingOptions, receipt?: RootPathObservationReceipt): Promise<RootDirectoryListing>;
|
|
@@ -105,10 +105,6 @@ export async function assertRootDirectoryObservationGuard(root, guard) {
|
|
|
105
105
|
throw directoryChangedError(error);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
function sameObservationDirectory(left, right) {
|
|
109
|
-
return left.dir === right.dir && left.realPath === right.realPath &&
|
|
110
|
-
left.identity.dev === right.identity.dev && left.identity.ino === right.identity.ino;
|
|
111
|
-
}
|
|
112
108
|
function assertReceiptDirectoryGuardSync(root, guard, backend) {
|
|
113
109
|
if (backend) {
|
|
114
110
|
const observed = inspectNativeDirectoryObservation(backend, guard.dir, guard.identity);
|
|
@@ -139,7 +135,8 @@ export function assertRootPathObservationReceiptCurrent(root, receipt, finalTarg
|
|
|
139
135
|
rootGuard.identity.dev !== root.rootIdentity.dev || rootGuard.identity.ino !== root.rootIdentity.ino) {
|
|
140
136
|
throw rootPathChangedError();
|
|
141
137
|
}
|
|
142
|
-
if (
|
|
138
|
+
if (rootGuard.dir === directoryGuard.dir && rootGuard.realPath === directoryGuard.realPath &&
|
|
139
|
+
rootGuard.identity.dev === directoryGuard.identity.dev && rootGuard.identity.ino === directoryGuard.identity.ino) {
|
|
143
140
|
try {
|
|
144
141
|
if (receipt.kind === "stat" && receipt.target === rootGuard && finalTarget &&
|
|
145
142
|
!receipt.directoryObserver) {
|
|
@@ -176,12 +173,6 @@ function normalizeDirectoryError(error) {
|
|
|
176
173
|
}
|
|
177
174
|
return error;
|
|
178
175
|
}
|
|
179
|
-
function normalizeInitialDirectoryError(error) {
|
|
180
|
-
if (error instanceof FsSafeError && error.code === "not-file") {
|
|
181
|
-
return new FsSafeError("not-found", "directory not found", { cause: error });
|
|
182
|
-
}
|
|
183
|
-
return normalizeDirectoryError(error);
|
|
184
|
-
}
|
|
185
176
|
export async function listDirectoryPath(root, directory, withFileTypes, receipt) {
|
|
186
177
|
let guard;
|
|
187
178
|
if (receipt) {
|
|
@@ -192,7 +183,10 @@ export async function listDirectoryPath(root, directory, withFileTypes, receipt)
|
|
|
192
183
|
guard = await createRootDirectoryObservationGuard(root, directory);
|
|
193
184
|
}
|
|
194
185
|
catch (error) {
|
|
195
|
-
|
|
186
|
+
if (error instanceof FsSafeError && error.code === "not-file") {
|
|
187
|
+
throw new FsSafeError("not-found", "directory not found", { cause: error });
|
|
188
|
+
}
|
|
189
|
+
throw normalizeDirectoryError(error);
|
|
196
190
|
}
|
|
197
191
|
}
|
|
198
192
|
return await listGuardedDirectoryPath(root, guard, withFileTypes, receipt);
|
|
@@ -29,7 +29,7 @@ export function hardlinkedPathNotAllowedError() {
|
|
|
29
29
|
export function isAlreadyExistsError(error) {
|
|
30
30
|
return hasNodeErrorCode(error, "EEXIST") || /File exists|EEXIST/i.test(String(error));
|
|
31
31
|
}
|
|
32
|
-
function
|
|
32
|
+
function resourceExhaustion(error) {
|
|
33
33
|
const pending = [error];
|
|
34
34
|
let details;
|
|
35
35
|
let code;
|
|
@@ -45,7 +45,11 @@ function descriptorExhaustion(error) {
|
|
|
45
45
|
const diagnostic = current;
|
|
46
46
|
if (current instanceof FsSafeError && (!details || current.details?.cleanup))
|
|
47
47
|
details = current.details ?? details;
|
|
48
|
-
if (diagnostic.code === "EMFILE" || diagnostic.code === "ENFILE")
|
|
48
|
+
if (diagnostic.code === "EMFILE" || diagnostic.code === "ENFILE") {
|
|
49
|
+
if (!code || code === "ENOSPC")
|
|
50
|
+
code = diagnostic.code;
|
|
51
|
+
}
|
|
52
|
+
else if (diagnostic.code === "ENOSPC")
|
|
49
53
|
code ??= diagnostic.code;
|
|
50
54
|
if (diagnostic.name === "SuppressedError")
|
|
51
55
|
pending.push(diagnostic.suppressed, diagnostic.error);
|
|
@@ -55,15 +59,32 @@ function descriptorExhaustion(error) {
|
|
|
55
59
|
}
|
|
56
60
|
if (!code)
|
|
57
61
|
return;
|
|
62
|
+
// Disk-space diagnostics retain the published direct-write code and already-classified errors.
|
|
63
|
+
if (code === "ENOSPC" && error instanceof FsSafeError)
|
|
64
|
+
return;
|
|
58
65
|
const cleanup = details?.cleanup;
|
|
59
66
|
const publication = details?.publication;
|
|
60
67
|
const preserved = cleanup?.status === "preserved" && publication?.status === "indeterminate";
|
|
61
|
-
|
|
68
|
+
const message = code === "ENOSPC" ? PINNED_WRITE_ERRNO_MESSAGES.get(code) : "filesystem write failed: too many open files";
|
|
69
|
+
return new FsSafeError(code === "ENOSPC" ? "invalid-path" : "helper-failed", `${message} (${code})${preserved ? "; publication outcome is indeterminate; staged file preserved" : ""}`, { cause: error, details });
|
|
62
70
|
}
|
|
63
71
|
export function normalizePinnedWriteError(error) {
|
|
64
|
-
const exhausted =
|
|
72
|
+
const exhausted = resourceExhaustion(error);
|
|
65
73
|
if (exhausted)
|
|
66
74
|
return exhausted;
|
|
75
|
+
// Disposal retains cleanup evidence, but the failed publication remains primary.
|
|
76
|
+
let primary = error;
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
try {
|
|
79
|
+
while (primary instanceof Error && primary.name === "SuppressedError" && !seen.has(primary)) {
|
|
80
|
+
seen.add(primary);
|
|
81
|
+
primary = primary.suppressed;
|
|
82
|
+
}
|
|
83
|
+
if (primary !== error && primary instanceof FsSafeError && primary.details?.phase === "publish") {
|
|
84
|
+
return new FsSafeError(primary.code, primary.message, { cause: error, details: primary.details });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch { /* Unreadable diagnostics retain the original failure. */ }
|
|
67
88
|
if (error instanceof FsSafeError) {
|
|
68
89
|
return error;
|
|
69
90
|
}
|
|
@@ -86,7 +86,6 @@ import { admitPathInsideRoot } from "./root-boundary.js";
|
|
|
86
86
|
import { listDirectoryPath, openRootDirectoryListing } from "./root-directory-list.js";
|
|
87
87
|
import { statResolvedPathInRoot } from "./root-path-stat.js";
|
|
88
88
|
import { entriesInRoot } from "./root-entries.js";
|
|
89
|
-
import { assertMoveMutationAllowed } from "./root-move-preflight.js";
|
|
90
89
|
import { assertRootIdentityCurrent, assertRootIdentityCurrentSync, assertValidRootDestinationPath, assertValidRootRelativePath, createRootObservationGuard, ensureTrailingSep, expandRelativePathWithHome, resolvePathInRoot, resolveRootContext, rootRelativeReadPath, } from "./root-context.js";
|
|
91
90
|
import { errorCauseOptions, fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, normalizePinnedWriteError, outsideWorkspaceError, } from "./root-errors.js";
|
|
92
91
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
@@ -96,7 +95,7 @@ import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
|
96
95
|
import { removePathInRootFallback, validateRemoveOptions } from "./root-remove.js";
|
|
97
96
|
import { removePathInRootNative } from "./root-remove-native.js";
|
|
98
97
|
import { serializePathWrite } from "./write-queue.js";
|
|
99
|
-
import { verifyAtomicWriteResult } from "./root-write-verification.js";
|
|
98
|
+
import { verifyAtomicWriteResult, verifyRootWritePublication } from "./root-write-verification.js";
|
|
100
99
|
import { assertRootWritePathSelectionSync, assertRootWriteSelectionSync, createRootWriteSelectionForFd, prepareGuardedRootWritePathSelection, resolveGuardedWritePathInRoot, resolveGuardedWriteTargetInRoot, resolvePinnedWriteTargetInRoot, refreshRetainedRootWriteAdmission, refreshRootWritePathSelection, } from "./root-write-admission.js";
|
|
101
100
|
import { prepareSharedRootWriteTarget } from "./root-write-complete-parent.js";
|
|
102
101
|
import { finishRootFallbackWrite } from "./root-write-publication.js";
|
|
@@ -107,7 +106,7 @@ import { admitMoveSourceStat, movePathNative } from "./root-move-noreplace.js";
|
|
|
107
106
|
import { admitRootReadHandle, inspectOpenedPathIdentitySync } from "./root-read-admission.js";
|
|
108
107
|
import { createCopyPublicationObserver, onCopyPublication, onCopySourceAdmission } from "./copy-publication.js";
|
|
109
108
|
import { writeAllToFile } from "./write-file-handle.js";
|
|
110
|
-
import { createInputOptions, rethrowCreateInputError, rootWriteInput } from "./root-create-input.js";
|
|
109
|
+
import { createInputOptions, rethrowCreateInputError, rootWriteInput, retainSidecarCreated } from "./root-create-input.js";
|
|
111
110
|
import { assertFinalSymlinkRejected, mutationSymlinkResolution, readSymlinkResolution } from "./root-symlink-policy.js";
|
|
112
111
|
import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
|
|
113
112
|
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot } from "./windows-path-alias.js";
|
|
@@ -390,11 +389,14 @@ export class RootHandle {
|
|
|
390
389
|
const { denyMutations, mutationSymlinks } = snapshotPinnedMutationPolicy(mutationOptions.denyMutations, mutationOptions.mutationSymlinks) ?? {};
|
|
391
390
|
const overwrite = options.overwrite ?? false;
|
|
392
391
|
const requireNative = isFsSafeNativeRequired();
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
392
|
+
// Deny both routes before source alias or identity admission.
|
|
393
|
+
for (const relativePath of [fromRelative, toRelative]) {
|
|
394
|
+
const { resolved } = await resolvePathInRoot(this.context, relativePath, {
|
|
395
|
+
aliasErrorCode: "path-alias",
|
|
396
|
+
allowFinalSymlink: true,
|
|
397
|
+
});
|
|
398
|
+
await assertMutationNotDenied(resolved, denyMutations, { protectAncestors: true });
|
|
399
|
+
}
|
|
398
400
|
await movePathFallback(this.context, {
|
|
399
401
|
requireNative,
|
|
400
402
|
fromRelative,
|
|
@@ -934,15 +936,13 @@ async function commitPinnedWriteInRoot(root, pinned, params, input) {
|
|
|
934
936
|
rootIdentity: root.rootIdentity,
|
|
935
937
|
mutationAdmission: pinned.mutationAdmission,
|
|
936
938
|
assertBeforeMutation: params.assertBeforeMutation,
|
|
937
|
-
verifyPublished: async (fd, expectedIdentity, parentGuard) => {
|
|
939
|
+
verifyPublished: async (fd, expectedIdentity, parentGuard, nativeParent) => {
|
|
938
940
|
verifyingPublication = true;
|
|
939
941
|
try {
|
|
940
|
-
await
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
fd,
|
|
944
|
-
expectedIdentity,
|
|
945
|
-
parentGuard,
|
|
942
|
+
await verifyRootWritePublication({
|
|
943
|
+
fd, expectedIdentity, parentGuard,
|
|
944
|
+
nativeParent, retention: params[retainSidecarCreated],
|
|
945
|
+
verify: () => verifyAtomicWriteResult({ root, targetPath: pinned.targetPath, fd, expectedIdentity, parentGuard }),
|
|
946
946
|
});
|
|
947
947
|
}
|
|
948
948
|
catch (error) {
|
|
@@ -1167,10 +1167,10 @@ async function movePathFallback(root, params) {
|
|
|
1167
1167
|
let sourceIdentity;
|
|
1168
1168
|
try {
|
|
1169
1169
|
if (params.overwrite && params.assertBeforeMutation) {
|
|
1170
|
-
sourceIdentity = inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(source.resolved, { bigint: true })
|
|
1170
|
+
sourceIdentity = inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(source.resolved, { bigint: true })));
|
|
1171
1171
|
}
|
|
1172
1172
|
else {
|
|
1173
|
-
admitMoveSourceStat(fsSync.lstatSync(source.resolved)
|
|
1173
|
+
admitMoveSourceStat(fsSync.lstatSync(source.resolved));
|
|
1174
1174
|
}
|
|
1175
1175
|
}
|
|
1176
1176
|
catch (error) {
|
|
@@ -1234,7 +1234,7 @@ async function movePathFallback(root, params) {
|
|
|
1234
1234
|
}
|
|
1235
1235
|
}
|
|
1236
1236
|
}
|
|
1237
|
-
inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(source.resolved, { bigint: true })
|
|
1237
|
+
inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(source.resolved, { bigint: true })), sourceIdentity);
|
|
1238
1238
|
}
|
|
1239
1239
|
await fs.rename(source.resolved, target.resolved);
|
|
1240
1240
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type BigIntStats, type Stats } from "node:fs";
|
|
2
2
|
import type { RootMoveOptions } from "./root-options.js";
|
|
3
3
|
import { type RootContext } from "./root-context.js";
|
|
4
|
-
export declare function admitMoveSourceStat<T extends Stats | BigIntStats>(stat: T
|
|
5
|
-
export declare function movePathNative(root: RootContext, params: Pick<RootMoveOptions, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"
|
|
4
|
+
export declare function admitMoveSourceStat<T extends Stats | BigIntStats>(stat: T): T;
|
|
5
|
+
export declare function movePathNative(root: RootContext, params: Pick<RootMoveOptions, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
|
|
6
|
+
requireNative: boolean;
|
|
7
|
+
}, paths: {
|
|
6
8
|
sourcePath: string;
|
|
7
9
|
sourceParentPath: string;
|
|
8
10
|
targetPath: string;
|
|
@@ -15,6 +15,7 @@ import { PATH_ALIAS_POLICIES } from "./path-policy.js";
|
|
|
15
15
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
16
16
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
17
17
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
18
|
+
import { nativeMoveNoReplace } from "./native-move-fallback.js";
|
|
18
19
|
function nativeParentRelativePath(rootReal, parentPath) {
|
|
19
20
|
const relative = path.relative(rootReal, parentPath);
|
|
20
21
|
if (relative === "" || relative === ".")
|
|
@@ -57,22 +58,13 @@ function normalizeMoveError(error) {
|
|
|
57
58
|
}
|
|
58
59
|
return error;
|
|
59
60
|
}
|
|
60
|
-
function
|
|
61
|
-
if (error?.code === "EINVAL") {
|
|
62
|
-
return new FsSafeError("helper-unavailable", "native no-replace move is unavailable on this filesystem", errorCauseOptions(error));
|
|
63
|
-
}
|
|
64
|
-
return normalizeMoveError(error);
|
|
65
|
-
}
|
|
66
|
-
export function admitMoveSourceStat(stat, overwrite = false) {
|
|
61
|
+
export function admitMoveSourceStat(stat) {
|
|
67
62
|
if (stat.isSymbolicLink()) {
|
|
68
63
|
throw new FsSafeError("symlink", "symlink not allowed");
|
|
69
64
|
}
|
|
70
65
|
if (stat.isFile() && stat.nlink > 1) {
|
|
71
66
|
throw hardlinkedPathNotAllowedError();
|
|
72
67
|
}
|
|
73
|
-
if (!overwrite && stat.isDirectory()) {
|
|
74
|
-
throw new FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
75
|
-
}
|
|
76
68
|
return stat;
|
|
77
69
|
}
|
|
78
70
|
export async function movePathNative(root, params, paths, overwrite = false) {
|
|
@@ -124,10 +116,9 @@ export async function movePathNative(root, params, paths, overwrite = false) {
|
|
|
124
116
|
await assertRootIdentityCurrent(root);
|
|
125
117
|
for (const admission of parentAdmissions)
|
|
126
118
|
assertSyncDirectoryGuard(admission.guard);
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
? inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true }), overwrite), paths.expectedSourceIdentity)
|
|
119
|
+
// Linux auto fallback needs an exact source even without an authority callback.
|
|
120
|
+
const sourceStat = overwrite || params.assertBeforeMutation || (process.platform === "linux" && !params.requireNative)
|
|
121
|
+
? inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true })), paths.expectedSourceIdentity)
|
|
131
122
|
: undefined;
|
|
132
123
|
if (!sourceStat)
|
|
133
124
|
admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath));
|
|
@@ -172,20 +163,25 @@ export async function movePathNative(root, params, paths, overwrite = false) {
|
|
|
172
163
|
}
|
|
173
164
|
}
|
|
174
165
|
if (sourceStat !== undefined) {
|
|
175
|
-
inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true })
|
|
166
|
+
inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true })), sourceStat);
|
|
176
167
|
}
|
|
177
168
|
try {
|
|
178
169
|
assertFinalSymlinkRejected(admittedTargetPath, params.mutationSymlinks !== undefined);
|
|
179
170
|
if (overwrite)
|
|
180
171
|
binding.renameReplaceWithIdentity(sourceParent.fd, path.basename(paths.sourcePath), targetParent.fd, path.basename(paths.targetPath), sourceStat.dev, sourceStat.ino);
|
|
181
172
|
else
|
|
182
|
-
binding
|
|
173
|
+
nativeMoveNoReplace(binding, [
|
|
174
|
+
sourceParent.fd,
|
|
175
|
+
path.basename(paths.sourcePath),
|
|
176
|
+
targetParent.fd,
|
|
177
|
+
path.basename(paths.targetPath),
|
|
178
|
+
], sourceStat, !params.requireNative);
|
|
183
179
|
}
|
|
184
180
|
catch (error) {
|
|
185
181
|
if (isAlreadyExistsError(error) || (!overwrite && error?.code === "ENOTEMPTY")) {
|
|
186
182
|
throw new FsSafeError("already-exists", "destination exists", errorCauseOptions(error));
|
|
187
183
|
}
|
|
188
|
-
throw
|
|
184
|
+
throw normalizeMoveError(error);
|
|
189
185
|
}
|
|
190
186
|
try {
|
|
191
187
|
for (const admission of parentAdmissions)
|
|
@@ -244,7 +244,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
|
|
|
244
244
|
if (!root) {
|
|
245
245
|
throw new FsSafeError("not-found", "root dir not found");
|
|
246
246
|
}
|
|
247
|
-
opened = await root.open(pathResult.relativePath);
|
|
247
|
+
opened = await root.open(`./${pathResult.relativePath}`);
|
|
248
248
|
resolvedPaths.push(opened.realPath);
|
|
249
249
|
}
|
|
250
250
|
catch (err) {
|
|
@@ -136,7 +136,9 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
136
136
|
let failed = false;
|
|
137
137
|
let operationError;
|
|
138
138
|
try {
|
|
139
|
-
const relativeDirectory = directory.split(path.sep).join(path.posix.sep);
|
|
139
|
+
const relativeDirectory = path.posix.normalize(directory.split(path.sep).join(path.posix.sep));
|
|
140
|
+
const childPrefix = relativeDirectory === "." || relativeDirectory === "./" ? ""
|
|
141
|
+
: relativeDirectory.endsWith("/") ? relativeDirectory : `${relativeDirectory}/`;
|
|
140
142
|
while (true) {
|
|
141
143
|
let next;
|
|
142
144
|
try {
|
|
@@ -150,9 +152,7 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
150
152
|
if (next === undefined)
|
|
151
153
|
return;
|
|
152
154
|
const name = next.kind === "entry" ? next.entry.name : next.name;
|
|
153
|
-
const child =
|
|
154
|
-
? path.posix.join(relativeDirectory, name)
|
|
155
|
-
: name;
|
|
155
|
+
const child = childPrefix + name;
|
|
156
156
|
if (next.kind === "limit") {
|
|
157
157
|
yield onLimit(child);
|
|
158
158
|
return;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
2
|
-
import type { PublishedWriteIdentity } from "./pinned-write-types.js";
|
|
2
|
+
import type { NativeWriteParent, PublishedWriteIdentity } from "./pinned-write-types.js";
|
|
3
|
+
import type { SidecarCreationRetention } from "./root-create-input.js";
|
|
3
4
|
import { type RootContext } from "./root-context.js";
|
|
5
|
+
export declare function verifyRootWritePublication(params: {
|
|
6
|
+
fd: number;
|
|
7
|
+
expectedIdentity: PublishedWriteIdentity;
|
|
8
|
+
parentGuard: AnyAsyncDirectoryGuard;
|
|
9
|
+
nativeParent?: NativeWriteParent;
|
|
10
|
+
retention?: SidecarCreationRetention;
|
|
11
|
+
verify(): Promise<void>;
|
|
12
|
+
}): Promise<void>;
|
|
4
13
|
export declare function verifyAtomicWriteResult(params: {
|
|
5
14
|
root: RootContext;
|
|
6
15
|
targetPath: string;
|
|
@@ -5,10 +5,30 @@ import { FsSafeError } from "./errors.js";
|
|
|
5
5
|
import { sameFileIdentity } from "./file-identity.js";
|
|
6
6
|
import { resolveOpenedFileRealPathForFd } from "./opened-realpath.js";
|
|
7
7
|
import { assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError } from "./path.js";
|
|
8
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
8
9
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
9
10
|
import { assertRootIdentityCurrent } from "./root-context.js";
|
|
10
11
|
import { fileNotFoundError, hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
|
|
11
12
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
13
|
+
export async function verifyRootWritePublication(params) {
|
|
14
|
+
try {
|
|
15
|
+
await params.verify();
|
|
16
|
+
params.retention?.retain(params.fd, params.parentGuard, params.nativeParent);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (params.retention && params.nativeParent) {
|
|
20
|
+
// Publication already happened. The creator still owns these descriptors
|
|
21
|
+
// even if opening an additional retained handle failed.
|
|
22
|
+
try {
|
|
23
|
+
params.retention.cleanup(params.fd, params.nativeParent, params.expectedIdentity);
|
|
24
|
+
}
|
|
25
|
+
catch (cleanupError) {
|
|
26
|
+
throw createSuppressedError(cleanupError, error, "sidecar retention and creator cleanup both failed");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
12
32
|
export async function verifyAtomicWriteResult(params) {
|
|
13
33
|
let needsPathOpen = false;
|
|
14
34
|
const assertFile = (stat) => {
|