@lousy-agents/cli 5.21.4 → 5.21.5
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/api/copilot-with-fastify/.devcontainer/devcontainer.json +1 -1
- package/api/copilot-with-fastify/package-lock.json +4 -4
- package/api/copilot-with-fastify/package.json +1 -1
- package/cli/copilot-with-citty/.devcontainer/devcontainer.json +1 -1
- package/cli/copilot-with-citty/package.json +1 -1
- package/dist/index.js +1810 -1581
- package/package.json +1 -1
- package/ui/copilot-with-react/.devcontainer/devcontainer.json +1 -1
- package/ui/copilot-with-react/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -3053,7 +3053,7 @@ exports.basename = (path, { windows } = {}) => {
|
|
|
3053
3053
|
|
|
3054
3054
|
|
|
3055
3055
|
},
|
|
3056
|
-
|
|
3056
|
+
8853(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
|
|
3057
3057
|
// NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
|
|
3058
3058
|
var constructs_namespaceObject = {};
|
|
3059
3059
|
__webpack_require__.r(constructs_namespaceObject);
|
|
@@ -3152,6 +3152,85 @@ function sameFileIdentityForCleanup(left, right, platform = process.platform) {
|
|
|
3152
3152
|
return sameStatValue(left.dev, right.dev) && sameStatValue(left.ino, right.ino);
|
|
3153
3153
|
}
|
|
3154
3154
|
|
|
3155
|
+
;// CONCATENATED MODULE: external "node:async_hooks"
|
|
3156
|
+
const external_node_async_hooks_namespaceObject = __rspack_createRequire_require("node:async_hooks");
|
|
3157
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/file-observation.js
|
|
3158
|
+
|
|
3159
|
+
const active = new external_node_async_hooks_namespaceObject.AsyncLocalStorage();
|
|
3160
|
+
function recordFileObservationFailure(error, kind) {
|
|
3161
|
+
const failures = active.getStore();
|
|
3162
|
+
if (!failures)
|
|
3163
|
+
return;
|
|
3164
|
+
let kinds = failures.get(error);
|
|
3165
|
+
if (!kinds)
|
|
3166
|
+
failures.set(error, kinds = new Set());
|
|
3167
|
+
kinds.add(kind);
|
|
3168
|
+
}
|
|
3169
|
+
function isFileObservationFailure(error, kind) {
|
|
3170
|
+
return active.getStore()?.get(error)?.has(kind) === true;
|
|
3171
|
+
}
|
|
3172
|
+
// Each Root observation owns its receipts, including nested and concurrent reads.
|
|
3173
|
+
function fileObservation() {
|
|
3174
|
+
const failures = new Map();
|
|
3175
|
+
return {
|
|
3176
|
+
run(operation) { return active.run(failures, operation); },
|
|
3177
|
+
has(error, kind) { return failures.get(error)?.has(kind) === true; },
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/strict-file-identity.js
|
|
3182
|
+
|
|
3183
|
+
|
|
3184
|
+
function identityMismatch() {
|
|
3185
|
+
const error = new errors_FsSafeError("path-mismatch", "file identity changed or could not be verified");
|
|
3186
|
+
recordFileObservationFailure(error, "identity");
|
|
3187
|
+
return error;
|
|
3188
|
+
}
|
|
3189
|
+
function identityCheck(expected, platform) {
|
|
3190
|
+
const known = {};
|
|
3191
|
+
const check = (stat) => {
|
|
3192
|
+
let complete = true;
|
|
3193
|
+
for (const field of ["dev", "ino"]) {
|
|
3194
|
+
const value = stat[field];
|
|
3195
|
+
// Numeric receipts cannot recover identity bits already lost to rounding.
|
|
3196
|
+
if (typeof value !== "bigint")
|
|
3197
|
+
throw identityMismatch();
|
|
3198
|
+
if (platform === "win32" && value === 0n) {
|
|
3199
|
+
complete = false;
|
|
3200
|
+
}
|
|
3201
|
+
else {
|
|
3202
|
+
if (known[field] !== undefined && known[field] !== value)
|
|
3203
|
+
throw identityMismatch();
|
|
3204
|
+
known[field] = value;
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
return complete;
|
|
3208
|
+
};
|
|
3209
|
+
if (expected && !check(expected))
|
|
3210
|
+
throw identityMismatch();
|
|
3211
|
+
return check;
|
|
3212
|
+
}
|
|
3213
|
+
// Retry only unknown Windows identities, retaining every known component so a
|
|
3214
|
+
// later observation cannot erase a definite mismatch. Never reopen the file.
|
|
3215
|
+
async function inspectFileIdentity(inspect, expected, platform = process.platform) {
|
|
3216
|
+
const check = identityCheck(expected, platform);
|
|
3217
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3218
|
+
const stat = await inspect();
|
|
3219
|
+
if (check(stat))
|
|
3220
|
+
return stat;
|
|
3221
|
+
}
|
|
3222
|
+
throw identityMismatch();
|
|
3223
|
+
}
|
|
3224
|
+
function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
|
|
3225
|
+
const check = identityCheck(expected, platform);
|
|
3226
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3227
|
+
const stat = inspect();
|
|
3228
|
+
if (check(stat))
|
|
3229
|
+
return stat;
|
|
3230
|
+
}
|
|
3231
|
+
throw identityMismatch();
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3155
3234
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path.js
|
|
3156
3235
|
|
|
3157
3236
|
|
|
@@ -3366,6 +3445,7 @@ function normalizeRemovePathError(error) {
|
|
|
3366
3445
|
|
|
3367
3446
|
|
|
3368
3447
|
|
|
3448
|
+
|
|
3369
3449
|
async function directory_guard_createAsyncDirectoryGuard(dir) {
|
|
3370
3450
|
const stat = await promises_.lstat(dir);
|
|
3371
3451
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
@@ -3430,6 +3510,15 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
|
|
|
3430
3510
|
}
|
|
3431
3511
|
return directory_guard_createSyncDirectoryGuard(root);
|
|
3432
3512
|
}
|
|
3513
|
+
// Recovery receipts must retain every identity bit, including on Windows.
|
|
3514
|
+
async function inspectDirectoryIdentity(dir, expected) {
|
|
3515
|
+
return await inspectFileIdentity(async () => {
|
|
3516
|
+
const stat = await promises_.lstat(dir, { bigint: true });
|
|
3517
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
3518
|
+
throw root_errors_directoryComponentNotDirectoryError();
|
|
3519
|
+
return stat;
|
|
3520
|
+
}, expected);
|
|
3521
|
+
}
|
|
3433
3522
|
|
|
3434
3523
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/directory-durability.js
|
|
3435
3524
|
|
|
@@ -3871,6 +3960,8 @@ async function guardedRename(params) {
|
|
|
3871
3960
|
? await createNearestExistingDirectoryGuard(params.targetRoot, path.dirname(params.to))
|
|
3872
3961
|
: await createAsyncDirectoryGuard(path.dirname(params.to));
|
|
3873
3962
|
await withAsyncDirectoryGuards([sourceGuard, targetGuard], async () => {
|
|
3963
|
+
// Authority must survive all awaited guards; do not yield before rename dispatch.
|
|
3964
|
+
params.assertBeforeRename?.();
|
|
3874
3965
|
await fs.rename(params.from, params.to);
|
|
3875
3966
|
}, { verifyAfter: params.verifyAfter });
|
|
3876
3967
|
}
|
|
@@ -4040,6 +4131,49 @@ function mergeDenyMutationPolicies(defaultPolicy, callPolicy) {
|
|
|
4040
4131
|
};
|
|
4041
4132
|
}
|
|
4042
4133
|
|
|
4134
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-file-failure.js
|
|
4135
|
+
|
|
4136
|
+
|
|
4137
|
+
|
|
4138
|
+
function recordFileOpenFailure(error, filePath) {
|
|
4139
|
+
recordFileObservationFailure(error, `open:${filePath}`);
|
|
4140
|
+
throw error;
|
|
4141
|
+
}
|
|
4142
|
+
function openedPathResolutionError(error = new errors_FsSafeError("path-mismatch", "unable to resolve opened file path")) {
|
|
4143
|
+
recordFileObservationFailure(error, "resolution");
|
|
4144
|
+
return error;
|
|
4145
|
+
}
|
|
4146
|
+
async function recordPreOpenFileChange(error, handle, filePath, before, opened) {
|
|
4147
|
+
if (!isFileObservationFailure(error, "identity") || !before?.isFile() || !opened?.isFile() ||
|
|
4148
|
+
![0n, 1n].includes(before.nlink) || ![0n, 1n].includes(opened.nlink))
|
|
4149
|
+
return;
|
|
4150
|
+
try {
|
|
4151
|
+
await inspectFileIdentity(async () => before);
|
|
4152
|
+
const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), opened);
|
|
4153
|
+
if (current.isFile() && [0n, 1n].includes(current.nlink) &&
|
|
4154
|
+
(before.dev !== current.dev || before.ino !== current.ino)) {
|
|
4155
|
+
// A stale pathname sample is not proof that its former inode was unlinked.
|
|
4156
|
+
recordFileObservationFailure(error, `changed:${filePath}`);
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
catch {
|
|
4160
|
+
// Unknown, changed, or closed descriptor evidence never permits a discard.
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
async function recordOpenedFileFailure(error, handle, filePath, identity) {
|
|
4164
|
+
if ((!isFileObservationFailure(error, "resolution") && !isFileObservationFailure(error, "identity")) ||
|
|
4165
|
+
!identity.isFile() || identity.nlink > 1n)
|
|
4166
|
+
return;
|
|
4167
|
+
try {
|
|
4168
|
+
const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), identity);
|
|
4169
|
+
if (current.isFile() && current.nlink === 0n)
|
|
4170
|
+
recordFileObservationFailure(error, `unlinked:${filePath}`);
|
|
4171
|
+
}
|
|
4172
|
+
catch {
|
|
4173
|
+
// A closed, changed, or unknown descriptor provides no retry evidence.
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4043
4177
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-realpath.js
|
|
4044
4178
|
|
|
4045
4179
|
|
|
@@ -4079,6 +4213,12 @@ async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
|
4079
4213
|
}
|
|
4080
4214
|
catch (err) {
|
|
4081
4215
|
if (!path_isNotFoundPathError(err)) {
|
|
4216
|
+
// Windows can fail here on a deleted-but-open file. Brand only this
|
|
4217
|
+
// resolver operation; the caller must still prove unlink on the same fd.
|
|
4218
|
+
if (process.platform === "win32" && err instanceof Error &&
|
|
4219
|
+
["EPERM", "EBADF"].includes(err.code ?? "")) {
|
|
4220
|
+
throw openedPathResolutionError(err);
|
|
4221
|
+
}
|
|
4082
4222
|
throw err;
|
|
4083
4223
|
}
|
|
4084
4224
|
}
|
|
@@ -4086,7 +4226,7 @@ async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
|
4086
4226
|
if (parentResolved) {
|
|
4087
4227
|
return parentResolved;
|
|
4088
4228
|
}
|
|
4089
|
-
throw
|
|
4229
|
+
throw openedPathResolutionError();
|
|
4090
4230
|
}
|
|
4091
4231
|
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptions) {
|
|
4092
4232
|
let parentReal;
|
|
@@ -4580,7 +4720,7 @@ async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuar
|
|
|
4580
4720
|
let renamed = false;
|
|
4581
4721
|
let completed = false;
|
|
4582
4722
|
try {
|
|
4583
|
-
tempName =
|
|
4723
|
+
tempName = `.fs-safe-${(0,external_node_crypto_.randomUUID)()}.tmp`;
|
|
4584
4724
|
tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL)).fd;
|
|
4585
4725
|
tempIdentity = external_node_fs_.fstatSync(tempFd);
|
|
4586
4726
|
const verificationIdentity = external_node_fs_.fstatSync(tempFd, { bigint: true });
|
|
@@ -4790,7 +4930,7 @@ const UNCOMMITTED_RENAME_ERRORS = new Set([
|
|
|
4790
4930
|
"ENAMETOOLONG", "ENOENT", "ENOSPC", "ENOSYS", "ENOTDIR", "ENOTEMPTY", "ENOTSUP",
|
|
4791
4931
|
"EPERM", "EROFS", "ETXTBSY", "EXDEV",
|
|
4792
4932
|
]);
|
|
4793
|
-
function
|
|
4933
|
+
function native_staged_file_failure(error, details) {
|
|
4794
4934
|
const code = error instanceof errors_FsSafeError
|
|
4795
4935
|
? error.code
|
|
4796
4936
|
: error?.code === "EEXIST" ? "already-exists" : "helper-failed";
|
|
@@ -4922,10 +5062,10 @@ class NativeStagedFile {
|
|
|
4922
5062
|
catch (error) {
|
|
4923
5063
|
const { receipt: cleanup, error: cleanupError } = this.#finalize();
|
|
4924
5064
|
if (cleanupError) {
|
|
4925
|
-
throw
|
|
5065
|
+
throw native_staged_file_failure(new AggregateError([error, cleanupError], "preparation and cleanup failed"), { phase: "prepare", publication: cleanup.publication, cleanup });
|
|
4926
5066
|
}
|
|
4927
5067
|
if (cleanup.status === "preserved") {
|
|
4928
|
-
throw
|
|
5068
|
+
throw native_staged_file_failure(error, { phase: "prepare", publication: cleanup.publication, cleanup });
|
|
4929
5069
|
}
|
|
4930
5070
|
throw error;
|
|
4931
5071
|
}
|
|
@@ -4984,7 +5124,7 @@ class NativeStagedFile {
|
|
|
4984
5124
|
const publication = this.#state.status === "closed"
|
|
4985
5125
|
? this.#state.receipt.publication
|
|
4986
5126
|
: this.#state.publication;
|
|
4987
|
-
throw
|
|
5127
|
+
throw native_staged_file_failure(error, { phase: "publish", publication });
|
|
4988
5128
|
}
|
|
4989
5129
|
}
|
|
4990
5130
|
#finalize() {
|
|
@@ -5025,7 +5165,7 @@ class NativeStagedFile {
|
|
|
5025
5165
|
status: outcome,
|
|
5026
5166
|
resources,
|
|
5027
5167
|
});
|
|
5028
|
-
const error = errors.length ?
|
|
5168
|
+
const error = errors.length ? native_staged_file_failure(errors.length === 1 ? errors[0] : new AggregateError(errors, "staged cleanup failed"), { phase: "cleanup", publication: state.publication, cleanup: receipt }) : undefined;
|
|
5029
5169
|
this.#state = { status: "closed", receipt, error };
|
|
5030
5170
|
return this.#state;
|
|
5031
5171
|
}
|
|
@@ -5334,438 +5474,1184 @@ function bounded_read_readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
|
5334
5474
|
}
|
|
5335
5475
|
}
|
|
5336
5476
|
|
|
5337
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/
|
|
5338
|
-
|
|
5339
|
-
function
|
|
5340
|
-
return
|
|
5341
|
-
}
|
|
5342
|
-
function getFsSafeTestHooks() {
|
|
5343
|
-
return test_hooks_fsSafeTestHooks;
|
|
5344
|
-
}
|
|
5345
|
-
function __setFsSafeTestHooksForTest(hooks) {
|
|
5346
|
-
if (hooks && !allowFsSafeTestHooks()) {
|
|
5347
|
-
throw new Error("__setFsSafeTestHooksForTest is only available in tests");
|
|
5348
|
-
}
|
|
5349
|
-
test_hooks_fsSafeTestHooks = hooks;
|
|
5477
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/error-detail.js
|
|
5478
|
+
const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
|
|
5479
|
+
function formatErrorDetail(value) {
|
|
5480
|
+
return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
5350
5481
|
}
|
|
5351
5482
|
|
|
5352
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5483
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-symlink.js
|
|
5356
5484
|
|
|
5357
5485
|
|
|
5358
5486
|
|
|
5359
5487
|
|
|
5360
5488
|
|
|
5361
5489
|
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
function createSidecarLockOwnershipToken() {
|
|
5368
|
-
let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
|
|
5369
|
-
for (const byte of (0,external_node_crypto_.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
|
|
5370
|
-
for (let bit = 7; bit >= 0; bit -= 1) {
|
|
5371
|
-
token += byte & (1 << bit) ? "\t" : " ";
|
|
5372
|
-
}
|
|
5373
|
-
}
|
|
5374
|
-
return token;
|
|
5375
|
-
}
|
|
5376
|
-
function readSidecarLockOwnershipToken(raw) {
|
|
5377
|
-
return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
|
|
5378
|
-
}
|
|
5379
|
-
function serializeSidecarLockPayload(payload) {
|
|
5380
|
-
const ownershipToken = createSidecarLockOwnershipToken();
|
|
5381
|
-
return {
|
|
5382
|
-
raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
|
|
5383
|
-
ownershipToken,
|
|
5384
|
-
};
|
|
5385
|
-
}
|
|
5386
|
-
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
5387
|
-
const resolved = external_node_path_.resolve(lockPath);
|
|
5388
|
-
const lexicalRelative = external_node_path_.relative(lockRoot.rootDir, resolved);
|
|
5389
|
-
const relative = lexicalRelative !== ".." &&
|
|
5390
|
-
!lexicalRelative.startsWith(`..${external_node_path_.sep}`) &&
|
|
5391
|
-
!external_node_path_.isAbsolute(lexicalRelative)
|
|
5392
|
-
? lexicalRelative
|
|
5393
|
-
: external_node_path_.relative(lockRoot.rootReal, resolved);
|
|
5394
|
-
if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relative)) {
|
|
5395
|
-
throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
5490
|
+
function normalizeSymlinkResolutionError(error) {
|
|
5491
|
+
if (isSymlinkOpenError(error)) {
|
|
5492
|
+
throw new errors_FsSafeError("symlink", "symlink path could not be resolved", {
|
|
5493
|
+
cause: error instanceof Error ? error : undefined,
|
|
5494
|
+
});
|
|
5396
5495
|
}
|
|
5397
|
-
|
|
5496
|
+
if (!path_isNotFoundPathError(error))
|
|
5497
|
+
throw error;
|
|
5398
5498
|
}
|
|
5399
|
-
function
|
|
5400
|
-
if (parser) {
|
|
5401
|
-
return parser(raw);
|
|
5402
|
-
}
|
|
5499
|
+
async function resolveSymlinkHopPath(symlinkPath) {
|
|
5403
5500
|
try {
|
|
5404
|
-
|
|
5405
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
5501
|
+
return external_node_path_.resolve(await promises_.realpath(symlinkPath));
|
|
5406
5502
|
}
|
|
5407
|
-
catch {
|
|
5408
|
-
|
|
5503
|
+
catch (error) {
|
|
5504
|
+
normalizeSymlinkResolutionError(error);
|
|
5505
|
+
const linkTarget = await promises_.readlink(symlinkPath);
|
|
5506
|
+
return resolvePathViaExistingAncestor(external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget));
|
|
5409
5507
|
}
|
|
5410
5508
|
}
|
|
5411
|
-
|
|
5412
|
-
let handle;
|
|
5509
|
+
function root_path_symlink_resolveSymlinkHopPathSync(symlinkPath) {
|
|
5413
5510
|
try {
|
|
5414
|
-
|
|
5415
|
-
const lockRoot = options.lockRoot;
|
|
5416
|
-
const relative = relativeSidecarLockPath(lockRoot, lockPath);
|
|
5417
|
-
const opened = await lockRoot.open(relative).catch(async (error) => {
|
|
5418
|
-
if (error instanceof errors_FsSafeError && error.code === "path-mismatch") {
|
|
5419
|
-
// An owner can unlink after open. Prove absence through the same root;
|
|
5420
|
-
// acquisition retries create-only, so a later replacement stays guarded.
|
|
5421
|
-
try {
|
|
5422
|
-
await lockRoot.stat(relative);
|
|
5423
|
-
}
|
|
5424
|
-
catch (probeError) {
|
|
5425
|
-
if (probeError instanceof errors_FsSafeError && probeError.code === "not-found") {
|
|
5426
|
-
return null;
|
|
5427
|
-
}
|
|
5428
|
-
}
|
|
5429
|
-
}
|
|
5430
|
-
throw error;
|
|
5431
|
-
});
|
|
5432
|
-
if (!opened)
|
|
5433
|
-
return null;
|
|
5434
|
-
try {
|
|
5435
|
-
const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
|
|
5436
|
-
return {
|
|
5437
|
-
raw,
|
|
5438
|
-
payload: parseSidecarLockPayload(raw, options.parsePayload),
|
|
5439
|
-
stat: opened.stat,
|
|
5440
|
-
};
|
|
5441
|
-
}
|
|
5442
|
-
finally {
|
|
5443
|
-
await opened.handle.close().catch(() => undefined);
|
|
5444
|
-
}
|
|
5445
|
-
}
|
|
5446
|
-
const before = await promises_.lstat(lockPath);
|
|
5447
|
-
if (!before.isFile() || before.isSymbolicLink()) {
|
|
5448
|
-
if (options.rejectNonFile) {
|
|
5449
|
-
throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
5450
|
-
}
|
|
5451
|
-
return null;
|
|
5452
|
-
}
|
|
5453
|
-
await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
|
|
5454
|
-
const noFollow = process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
|
|
5455
|
-
? external_node_fs_.constants.O_NOFOLLOW
|
|
5456
|
-
: 0;
|
|
5457
|
-
try {
|
|
5458
|
-
handle = await promises_.open(lockPath, external_node_fs_.constants.O_RDONLY |
|
|
5459
|
-
noFollow |
|
|
5460
|
-
(typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0));
|
|
5461
|
-
}
|
|
5462
|
-
catch (error) {
|
|
5463
|
-
if (options.rejectNonFile && error.code === "ELOOP") {
|
|
5464
|
-
throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
|
|
5465
|
-
cause: error,
|
|
5466
|
-
});
|
|
5467
|
-
}
|
|
5468
|
-
throw error;
|
|
5469
|
-
}
|
|
5470
|
-
const opened = await handle.stat();
|
|
5471
|
-
if (!opened.isFile()) {
|
|
5472
|
-
if (options.rejectNonFile) {
|
|
5473
|
-
throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
5474
|
-
}
|
|
5475
|
-
return null;
|
|
5476
|
-
}
|
|
5477
|
-
if (!options.allowDescriptorIdentityDrift && !file_identity_sameFileIdentity(before, opened))
|
|
5478
|
-
return null;
|
|
5479
|
-
const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
|
|
5480
|
-
const after = await promises_.lstat(lockPath);
|
|
5481
|
-
if (!after.isFile() || !file_identity_sameFileIdentity(before, after))
|
|
5482
|
-
return null;
|
|
5483
|
-
return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat: after };
|
|
5484
|
-
}
|
|
5485
|
-
catch (err) {
|
|
5486
|
-
if (err.code === "ENOENT" ||
|
|
5487
|
-
(err instanceof errors_FsSafeError && err.code === "not-found")) {
|
|
5488
|
-
return null;
|
|
5489
|
-
}
|
|
5490
|
-
throw err;
|
|
5511
|
+
return path.resolve(fs.realpathSync(symlinkPath));
|
|
5491
5512
|
}
|
|
5492
|
-
|
|
5493
|
-
|
|
5513
|
+
catch (error) {
|
|
5514
|
+
normalizeSymlinkResolutionError(error);
|
|
5515
|
+
const linkTarget = fs.readlinkSync(symlinkPath);
|
|
5516
|
+
return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
|
|
5494
5517
|
}
|
|
5495
5518
|
}
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
}
|
|
5512
|
-
return null;
|
|
5513
|
-
}
|
|
5514
|
-
const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
|
|
5515
|
-
const after = fsSync.lstatSync(lockPath);
|
|
5516
|
-
if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
|
|
5517
|
-
return null;
|
|
5518
|
-
return {
|
|
5519
|
-
raw,
|
|
5520
|
-
payload: parseSidecarLockPayload(raw, parsePayload),
|
|
5521
|
-
stat: after,
|
|
5522
|
-
ownershipToken: readSidecarLockOwnershipToken(raw),
|
|
5523
|
-
};
|
|
5519
|
+
|
|
5520
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/safe-path-segment.js
|
|
5521
|
+
|
|
5522
|
+
const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
|
|
5523
|
+
const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
5524
|
+
// Windows treats "C:name" as relative to the drive's current directory even
|
|
5525
|
+
// though path.win32.isAbsolute() reports false.
|
|
5526
|
+
const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
|
|
5527
|
+
const HYPHEN_CHAR_CODE = 0x2d;
|
|
5528
|
+
function safe_path_segment_isDriveRelativePath(value) {
|
|
5529
|
+
return DRIVE_RELATIVE_PREFIX.test(value);
|
|
5530
|
+
}
|
|
5531
|
+
function assertNoDriveRelativePathSegments(value, label) {
|
|
5532
|
+
if (value.split("/").some(safe_path_segment_isDriveRelativePath)) {
|
|
5533
|
+
throw new errors_FsSafeError("invalid-path", `${label} must not contain a drive letter`);
|
|
5524
5534
|
}
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5535
|
+
return value;
|
|
5536
|
+
}
|
|
5537
|
+
function trimHyphenEdges(value) {
|
|
5538
|
+
let start = 0;
|
|
5539
|
+
let end = value.length;
|
|
5540
|
+
while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
|
|
5541
|
+
start += 1;
|
|
5529
5542
|
}
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
fsSync.closeSync(fd);
|
|
5543
|
+
while (end > start && value.charCodeAt(end - 1) === HYPHEN_CHAR_CODE) {
|
|
5544
|
+
end -= 1;
|
|
5533
5545
|
}
|
|
5546
|
+
return start === 0 && end === value.length ? value : value.slice(start, end);
|
|
5534
5547
|
}
|
|
5535
|
-
function
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5548
|
+
function isSafePathSegment(segment, options = {}) {
|
|
5549
|
+
return (segment !== "" &&
|
|
5550
|
+
segment !== "." &&
|
|
5551
|
+
segment !== ".." &&
|
|
5552
|
+
!segment.includes("/") &&
|
|
5553
|
+
!segment.includes("\\") &&
|
|
5554
|
+
!segment.includes("\0") &&
|
|
5555
|
+
(options.allowDotPrefix === true || !segment.startsWith(".")) &&
|
|
5556
|
+
(options.allowDotPrefix === true
|
|
5557
|
+
? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
|
|
5558
|
+
: SAFE_PATH_SEGMENT_PATTERN.test(segment)));
|
|
5541
5559
|
}
|
|
5542
|
-
function
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
|
|
5548
|
-
readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
|
|
5549
|
-
current.raw === observed.raw);
|
|
5550
|
-
}
|
|
5551
|
-
if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
|
|
5552
|
-
return false;
|
|
5560
|
+
function assertSafePathSegment(segment, options = {}) {
|
|
5561
|
+
// Validate the exact value callers will later join into paths; trimming here
|
|
5562
|
+
// would let whitespace-padded ids pass and then be used verbatim.
|
|
5563
|
+
if (!isSafePathSegment(segment, options)) {
|
|
5564
|
+
throw new FsSafeError("invalid-path", `${options.label ?? "path segment"} must be a safe path segment`);
|
|
5553
5565
|
}
|
|
5554
|
-
|
|
5555
|
-
|
|
5566
|
+
return segment;
|
|
5567
|
+
}
|
|
5568
|
+
function sanitizeSafePathSegment(value, fallback, options = {}) {
|
|
5569
|
+
const sanitized = value
|
|
5570
|
+
.trim()
|
|
5571
|
+
.replace(/[\\/]+/g, "-")
|
|
5572
|
+
.replace(/\0/g, "")
|
|
5573
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-");
|
|
5574
|
+
const trimmed = trimHyphenEdges(sanitized);
|
|
5575
|
+
if (isSafePathSegment(trimmed, options)) {
|
|
5576
|
+
return trimmed;
|
|
5556
5577
|
}
|
|
5557
|
-
return
|
|
5578
|
+
return assertSafePathSegment(fallback, { ...options, label: "fallback path segment" });
|
|
5558
5579
|
}
|
|
5559
|
-
|
|
5560
|
-
|
|
5580
|
+
function assertSafePathPrefix(prefix, options = {}) {
|
|
5581
|
+
// Prefixes are often derived from safe filenames. Normalize harmless
|
|
5582
|
+
// filename characters first, but still reject real path-control bytes.
|
|
5583
|
+
if (prefix.includes("/") || prefix.includes("\\") || prefix.includes("\0")) {
|
|
5584
|
+
return assertSafePathSegment(prefix, {
|
|
5585
|
+
allowDotPrefix: true,
|
|
5586
|
+
...options,
|
|
5587
|
+
label: options.label ?? "path prefix",
|
|
5588
|
+
});
|
|
5589
|
+
}
|
|
5590
|
+
return assertSafePathSegment(prefix.replace(/[^A-Za-z0-9._-]+/g, "-"), {
|
|
5591
|
+
allowDotPrefix: true,
|
|
5561
5592
|
...options,
|
|
5562
|
-
|
|
5593
|
+
label: options.label ?? "path prefix",
|
|
5563
5594
|
});
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5595
|
+
}
|
|
5596
|
+
|
|
5597
|
+
// EXTERNAL MODULE: external "node:os"
|
|
5598
|
+
var external_node_os_ = __webpack_require__(8161);
|
|
5599
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/short-path.js
|
|
5600
|
+
|
|
5601
|
+
|
|
5602
|
+
function shortPath(value) {
|
|
5603
|
+
const home = external_node_os_.homedir();
|
|
5604
|
+
const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
|
|
5605
|
+
return formatErrorDetail(shortened);
|
|
5606
|
+
}
|
|
5607
|
+
|
|
5608
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
|
|
5609
|
+
|
|
5610
|
+
|
|
5611
|
+
|
|
5612
|
+
|
|
5613
|
+
|
|
5614
|
+
|
|
5615
|
+
|
|
5616
|
+
|
|
5617
|
+
|
|
5618
|
+
|
|
5619
|
+
|
|
5620
|
+
const ROOT_PATH_ALIAS_POLICIES = {
|
|
5621
|
+
strict: Object.freeze({
|
|
5622
|
+
allowFinalSymlinkForUnlink: false,
|
|
5623
|
+
allowFinalHardlinkForUnlink: false,
|
|
5624
|
+
}),
|
|
5625
|
+
unlinkTarget: Object.freeze({
|
|
5626
|
+
allowFinalSymlinkForUnlink: true,
|
|
5627
|
+
allowFinalHardlinkForUnlink: true,
|
|
5628
|
+
}),
|
|
5629
|
+
};
|
|
5630
|
+
async function resolveRootPath(params) {
|
|
5631
|
+
try {
|
|
5632
|
+
return await resolveRootPathInternal(params);
|
|
5569
5633
|
}
|
|
5570
|
-
|
|
5571
|
-
|
|
5634
|
+
catch (error) {
|
|
5635
|
+
throw sanitizeRootPathError(error);
|
|
5572
5636
|
}
|
|
5573
|
-
return true;
|
|
5574
5637
|
}
|
|
5575
|
-
async function
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5638
|
+
async function resolveRootPathInternal(params) {
|
|
5639
|
+
assertValidRootPathInputs(params);
|
|
5640
|
+
const rootPath = external_node_path_.resolve(params.rootPath);
|
|
5641
|
+
const absolutePath = external_node_path_.resolve(params.absolutePath);
|
|
5642
|
+
const rootCanonicalPath = params.rootCanonicalPath
|
|
5643
|
+
? external_node_path_.resolve(params.rootCanonicalPath)
|
|
5644
|
+
: await resolvePathViaExistingAncestor(rootPath);
|
|
5645
|
+
const context = createBoundaryResolutionContext({
|
|
5646
|
+
resolveParams: params,
|
|
5647
|
+
rootPath,
|
|
5648
|
+
absolutePath,
|
|
5649
|
+
rootCanonicalPath,
|
|
5650
|
+
outsideLexicalCanonicalPath: await resolveOutsideLexicalCanonicalPathAsync({
|
|
5651
|
+
rootPath,
|
|
5652
|
+
absolutePath,
|
|
5653
|
+
}),
|
|
5654
|
+
});
|
|
5655
|
+
const outsideResult = await resolveOutsideRootPathAsync({
|
|
5656
|
+
boundaryLabel: params.boundaryLabel,
|
|
5657
|
+
context,
|
|
5658
|
+
});
|
|
5659
|
+
if (outsideResult) {
|
|
5660
|
+
return outsideResult;
|
|
5661
|
+
}
|
|
5662
|
+
return resolveRootPathLexicalAsync({
|
|
5663
|
+
params,
|
|
5664
|
+
absolutePath: context.absolutePath,
|
|
5665
|
+
rootPath: context.rootPath,
|
|
5666
|
+
rootCanonicalPath: context.rootCanonicalPath,
|
|
5579
5667
|
});
|
|
5580
|
-
return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
|
|
5581
5668
|
}
|
|
5582
|
-
|
|
5669
|
+
function resolveRootPathSync(params) {
|
|
5583
5670
|
try {
|
|
5584
|
-
|
|
5585
|
-
return true;
|
|
5671
|
+
return resolveRootPathSyncInternal(params);
|
|
5586
5672
|
}
|
|
5587
|
-
catch (
|
|
5588
|
-
|
|
5589
|
-
return false;
|
|
5590
|
-
}
|
|
5591
|
-
throw err;
|
|
5673
|
+
catch (error) {
|
|
5674
|
+
throw sanitizeRootPathError(error);
|
|
5592
5675
|
}
|
|
5593
5676
|
}
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5677
|
+
function resolveRootPathSyncInternal(params) {
|
|
5678
|
+
assertValidRootPathInputs(params);
|
|
5679
|
+
const rootPath = path.resolve(params.rootPath);
|
|
5680
|
+
const absolutePath = path.resolve(params.absolutePath);
|
|
5681
|
+
const rootCanonicalPath = params.rootCanonicalPath
|
|
5682
|
+
? path.resolve(params.rootCanonicalPath)
|
|
5683
|
+
: resolvePathViaExistingAncestorSync(rootPath);
|
|
5684
|
+
const context = createBoundaryResolutionContext({
|
|
5685
|
+
resolveParams: params,
|
|
5686
|
+
rootPath,
|
|
5687
|
+
absolutePath,
|
|
5688
|
+
rootCanonicalPath,
|
|
5689
|
+
outsideLexicalCanonicalPath: resolveOutsideLexicalCanonicalPathSync({
|
|
5690
|
+
rootPath,
|
|
5691
|
+
absolutePath,
|
|
5692
|
+
}),
|
|
5693
|
+
});
|
|
5694
|
+
const outsideResult = resolveOutsideRootPathSync({
|
|
5695
|
+
boundaryLabel: params.boundaryLabel,
|
|
5696
|
+
context,
|
|
5697
|
+
});
|
|
5698
|
+
if (outsideResult) {
|
|
5699
|
+
return outsideResult;
|
|
5599
5700
|
}
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5701
|
+
return resolveRootPathLexicalSync({
|
|
5702
|
+
params,
|
|
5703
|
+
absolutePath: context.absolutePath,
|
|
5704
|
+
rootPath: context.rootPath,
|
|
5705
|
+
rootCanonicalPath: context.rootCanonicalPath,
|
|
5706
|
+
});
|
|
5707
|
+
}
|
|
5708
|
+
function sanitizeRootPathError(error) {
|
|
5709
|
+
if (error instanceof Error) {
|
|
5710
|
+
error.message = formatErrorDetail(error.message);
|
|
5605
5711
|
}
|
|
5712
|
+
return error;
|
|
5606
5713
|
}
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5714
|
+
function assertValidRootPathInputs(params) {
|
|
5715
|
+
path_assertNoNulPathInput(params.rootPath, "root path contains a NUL byte");
|
|
5716
|
+
path_assertNoNulPathInput(params.absolutePath, "absolute path contains a NUL byte");
|
|
5717
|
+
assertNoEmbeddedDriveRelativeSegment(params.rootPath, "root path");
|
|
5718
|
+
assertNoEmbeddedDriveRelativeSegment(params.absolutePath, "absolute path");
|
|
5719
|
+
if (params.rootCanonicalPath !== undefined) {
|
|
5720
|
+
path_assertNoNulPathInput(params.rootCanonicalPath, "canonical root path contains a NUL byte");
|
|
5721
|
+
assertNoEmbeddedDriveRelativeSegment(params.rootCanonicalPath, "canonical root path");
|
|
5722
|
+
}
|
|
5610
5723
|
}
|
|
5611
|
-
|
|
5612
|
-
if (
|
|
5613
|
-
return
|
|
5724
|
+
function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
|
|
5725
|
+
if (process.platform !== "win32") {
|
|
5726
|
+
return;
|
|
5614
5727
|
}
|
|
5615
|
-
const
|
|
5616
|
-
|
|
5617
|
-
|
|
5728
|
+
const root = external_node_path_.parse(filePath).root;
|
|
5729
|
+
assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
|
|
5730
|
+
}
|
|
5731
|
+
function isPromiseLike(value) {
|
|
5732
|
+
return Boolean(value &&
|
|
5733
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
5734
|
+
"then" in value &&
|
|
5735
|
+
typeof value.then === "function");
|
|
5736
|
+
}
|
|
5737
|
+
function createLexicalTraversalState(params) {
|
|
5738
|
+
const rawAbsolutePath = params.params.absolutePath;
|
|
5739
|
+
const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
|
|
5740
|
+
const relative = rawRelativePath ?? external_node_path_.relative(params.rootPath, params.absolutePath);
|
|
5741
|
+
return {
|
|
5742
|
+
segments: splitTraversalSegments(relative),
|
|
5743
|
+
allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
|
|
5744
|
+
canonicalCursor: params.rootCanonicalPath,
|
|
5745
|
+
lexicalCursor: params.rootPath,
|
|
5746
|
+
preserveFinalSymlink: false,
|
|
5747
|
+
};
|
|
5748
|
+
}
|
|
5749
|
+
function createLexicalTraversalContext(params) {
|
|
5750
|
+
return {
|
|
5751
|
+
state: createLexicalTraversalState(params),
|
|
5752
|
+
resolveParams: params.params,
|
|
5753
|
+
rootPath: params.rootPath,
|
|
5754
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
5755
|
+
absolutePath: params.absolutePath,
|
|
5756
|
+
};
|
|
5757
|
+
}
|
|
5758
|
+
function splitTraversalSegments(value) {
|
|
5759
|
+
return value
|
|
5760
|
+
.split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
|
|
5761
|
+
.filter((segment) => Boolean(segment) && segment !== ".");
|
|
5762
|
+
}
|
|
5763
|
+
function rawPathRelativeToRoot(rootPath, candidatePath) {
|
|
5764
|
+
if (!external_node_path_.isAbsolute(candidatePath)) {
|
|
5765
|
+
return undefined;
|
|
5618
5766
|
}
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
return "not-approved";
|
|
5767
|
+
const root = external_node_path_.resolve(rootPath);
|
|
5768
|
+
const candidate = process.platform === "win32"
|
|
5769
|
+
? candidatePath.replaceAll("/", external_node_path_.sep)
|
|
5770
|
+
: candidatePath;
|
|
5771
|
+
if (candidate === root) {
|
|
5772
|
+
return "";
|
|
5626
5773
|
}
|
|
5627
|
-
|
|
5628
|
-
|
|
5774
|
+
const rootWithSep = root.endsWith(external_node_path_.sep) ? root : `${root}${external_node_path_.sep}`;
|
|
5775
|
+
const candidatePrefix = candidate.slice(0, rootWithSep.length);
|
|
5776
|
+
const prefixMatches = process.platform === "win32"
|
|
5777
|
+
? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
|
|
5778
|
+
: candidatePrefix === rootWithSep;
|
|
5779
|
+
return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
|
|
5780
|
+
}
|
|
5781
|
+
function assertLexicalCursorInsideBoundary(context, candidatePath) {
|
|
5782
|
+
assertInsideBoundary({
|
|
5783
|
+
boundaryLabel: context.resolveParams.boundaryLabel,
|
|
5784
|
+
rootCanonicalPath: context.rootCanonicalPath,
|
|
5785
|
+
candidatePath,
|
|
5786
|
+
absolutePath: context.absolutePath,
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
|
|
5790
|
+
const missingSuffix = context.state.segments.slice(missingFromIndex);
|
|
5791
|
+
for (const segment of missingSuffix) {
|
|
5792
|
+
advanceCanonicalCursorForSegment(context, segment);
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
function advanceCanonicalCursorForSegment(context, segment) {
|
|
5796
|
+
context.state.canonicalCursor = external_node_path_.resolve(context.state.canonicalCursor, segment);
|
|
5797
|
+
assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
|
|
5798
|
+
}
|
|
5799
|
+
function finalizeLexicalResolution(context, kind) {
|
|
5800
|
+
assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
|
|
5801
|
+
return buildResolvedRootPath({
|
|
5802
|
+
absolutePath: context.absolutePath,
|
|
5803
|
+
canonicalPath: context.state.canonicalCursor,
|
|
5804
|
+
rootPath: context.rootPath,
|
|
5805
|
+
rootCanonicalPath: context.rootCanonicalPath,
|
|
5806
|
+
kind,
|
|
5807
|
+
});
|
|
5808
|
+
}
|
|
5809
|
+
function handleLexicalLstatFailure(context, error, missingFromIndex) {
|
|
5810
|
+
if (!path_isNotFoundPathError(error)) {
|
|
5811
|
+
return false;
|
|
5812
|
+
}
|
|
5813
|
+
applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
|
|
5814
|
+
return true;
|
|
5815
|
+
}
|
|
5816
|
+
function handleLexicalStatReadFailure(context, error, missingFromIndex) {
|
|
5817
|
+
if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
|
|
5818
|
+
return null;
|
|
5819
|
+
}
|
|
5820
|
+
throw error;
|
|
5821
|
+
}
|
|
5822
|
+
function handleLexicalStatDisposition(context, params) {
|
|
5823
|
+
if (!params.isSymbolicLink) {
|
|
5824
|
+
advanceCanonicalCursorForSegment(context, params.segment);
|
|
5825
|
+
return "continue";
|
|
5826
|
+
}
|
|
5827
|
+
if (context.resolveParams.rejectSymlinks === true && params.isLast) {
|
|
5828
|
+
throw new errors_FsSafeError("symlink", "symlink path component not allowed");
|
|
5829
|
+
}
|
|
5830
|
+
if (context.state.allowFinalSymlink && params.isLast) {
|
|
5831
|
+
context.state.preserveFinalSymlink = true;
|
|
5832
|
+
advanceCanonicalCursorForSegment(context, params.segment);
|
|
5833
|
+
return "break";
|
|
5834
|
+
}
|
|
5835
|
+
return "resolve-link";
|
|
5836
|
+
}
|
|
5837
|
+
function applyResolvedSymlinkHop(context, linkCanonical) {
|
|
5838
|
+
if (!path_isPathInside(context.rootCanonicalPath, linkCanonical)) {
|
|
5839
|
+
throw symlinkEscapeError({
|
|
5840
|
+
boundaryLabel: context.resolveParams.boundaryLabel,
|
|
5841
|
+
rootCanonicalPath: context.rootCanonicalPath,
|
|
5842
|
+
symlinkPath: context.state.lexicalCursor,
|
|
5843
|
+
});
|
|
5629
5844
|
}
|
|
5845
|
+
context.state.canonicalCursor = linkCanonical;
|
|
5846
|
+
context.state.lexicalCursor = linkCanonical;
|
|
5847
|
+
}
|
|
5848
|
+
function readLexicalStat(context, params) {
|
|
5630
5849
|
try {
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
else {
|
|
5635
|
-
await promises_.rm(params.lockPath);
|
|
5850
|
+
const stat = params.read(context.state.lexicalCursor);
|
|
5851
|
+
if (isPromiseLike(stat)) {
|
|
5852
|
+
return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
|
|
5636
5853
|
}
|
|
5637
|
-
return
|
|
5854
|
+
return stat;
|
|
5638
5855
|
}
|
|
5639
|
-
catch (
|
|
5640
|
-
|
|
5641
|
-
|
|
5856
|
+
catch (error) {
|
|
5857
|
+
return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
|
|
5858
|
+
}
|
|
5859
|
+
}
|
|
5860
|
+
function resolveAndApplySymlinkHop(context, params) {
|
|
5861
|
+
const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
|
|
5862
|
+
if (isPromiseLike(linkCanonical)) {
|
|
5863
|
+
return Promise.resolve(linkCanonical).then((value) => {
|
|
5864
|
+
applyResolvedSymlinkHop(context, value);
|
|
5865
|
+
});
|
|
5866
|
+
}
|
|
5867
|
+
applyResolvedSymlinkHop(context, linkCanonical);
|
|
5868
|
+
}
|
|
5869
|
+
function applyParentTraversalStep(context) {
|
|
5870
|
+
context.state.lexicalCursor = external_node_path_.resolve(context.state.lexicalCursor, "..");
|
|
5871
|
+
advanceCanonicalCursorForSegment(context, "..");
|
|
5872
|
+
}
|
|
5873
|
+
function* iterateLexicalTraversal(state) {
|
|
5874
|
+
for (let idx = 0; idx < state.segments.length; idx += 1) {
|
|
5875
|
+
const segment = state.segments[idx] ?? "";
|
|
5876
|
+
const isLast = idx === state.segments.length - 1;
|
|
5877
|
+
yield { idx, segment, isLast };
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
async function resolveRootPathLexicalAsync(params) {
|
|
5881
|
+
const context = createLexicalTraversalContext(params);
|
|
5882
|
+
const { state } = context;
|
|
5883
|
+
for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
|
|
5884
|
+
if (segment === "..") {
|
|
5885
|
+
applyParentTraversalStep(context);
|
|
5886
|
+
continue;
|
|
5887
|
+
}
|
|
5888
|
+
state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
|
|
5889
|
+
const stat = await readLexicalStat(context, {
|
|
5890
|
+
missingFromIndex: idx,
|
|
5891
|
+
read: (cursor) => promises_.lstat(cursor),
|
|
5892
|
+
});
|
|
5893
|
+
if (!stat) {
|
|
5894
|
+
break;
|
|
5895
|
+
}
|
|
5896
|
+
const disposition = handleLexicalStatDisposition(context, {
|
|
5897
|
+
isSymbolicLink: stat.isSymbolicLink(),
|
|
5898
|
+
segment,
|
|
5899
|
+
isLast,
|
|
5900
|
+
});
|
|
5901
|
+
if (disposition === "continue") {
|
|
5902
|
+
continue;
|
|
5903
|
+
}
|
|
5904
|
+
if (disposition === "break") {
|
|
5905
|
+
break;
|
|
5906
|
+
}
|
|
5907
|
+
await resolveAndApplySymlinkHop(context, {
|
|
5908
|
+
resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
|
|
5909
|
+
});
|
|
5910
|
+
if (context.resolveParams.rejectSymlinks === true) {
|
|
5911
|
+
throw new errors_FsSafeError("symlink", "symlink path component not allowed");
|
|
5642
5912
|
}
|
|
5643
|
-
throw err;
|
|
5644
5913
|
}
|
|
5914
|
+
const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
|
|
5915
|
+
return finalizeLexicalResolution(context, kind);
|
|
5645
5916
|
}
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5917
|
+
function resolveRootPathLexicalSync(params) {
|
|
5918
|
+
const context = createLexicalTraversalContext(params);
|
|
5919
|
+
const { state } = context;
|
|
5920
|
+
for (let idx = 0; idx < state.segments.length; idx += 1) {
|
|
5921
|
+
const segment = state.segments[idx] ?? "";
|
|
5922
|
+
const isLast = idx === state.segments.length - 1;
|
|
5923
|
+
if (segment === "..") {
|
|
5924
|
+
applyParentTraversalStep(context);
|
|
5925
|
+
continue;
|
|
5926
|
+
}
|
|
5927
|
+
state.lexicalCursor = path.join(state.lexicalCursor, segment);
|
|
5928
|
+
const maybeStat = readLexicalStat(context, {
|
|
5929
|
+
missingFromIndex: idx,
|
|
5930
|
+
read: (cursor) => fs.lstatSync(cursor),
|
|
5931
|
+
});
|
|
5932
|
+
if (isPromiseLike(maybeStat)) {
|
|
5933
|
+
throw new Error("Unexpected async lexical stat");
|
|
5934
|
+
}
|
|
5935
|
+
const stat = maybeStat;
|
|
5936
|
+
if (!stat) {
|
|
5937
|
+
break;
|
|
5938
|
+
}
|
|
5939
|
+
const disposition = handleLexicalStatDisposition(context, {
|
|
5940
|
+
isSymbolicLink: stat.isSymbolicLink(),
|
|
5941
|
+
segment,
|
|
5942
|
+
isLast,
|
|
5943
|
+
});
|
|
5944
|
+
if (disposition === "continue") {
|
|
5945
|
+
continue;
|
|
5946
|
+
}
|
|
5947
|
+
if (disposition === "break") {
|
|
5948
|
+
break;
|
|
5949
|
+
}
|
|
5950
|
+
const maybeApplied = resolveAndApplySymlinkHop(context, {
|
|
5951
|
+
resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
|
|
5952
|
+
});
|
|
5953
|
+
if (isPromiseLike(maybeApplied)) {
|
|
5954
|
+
throw new Error("Unexpected async symlink resolution");
|
|
5955
|
+
}
|
|
5956
|
+
if (context.resolveParams.rejectSymlinks === true) {
|
|
5957
|
+
throw new FsSafeError("symlink", "symlink path component not allowed");
|
|
5958
|
+
}
|
|
5652
5959
|
}
|
|
5960
|
+
const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
|
|
5961
|
+
return finalizeLexicalResolution(context, kind);
|
|
5653
5962
|
}
|
|
5654
|
-
function
|
|
5655
|
-
|
|
5656
|
-
|
|
5963
|
+
function resolveCanonicalOutsideLexicalPath(params) {
|
|
5964
|
+
return params.outsideLexicalCanonicalPath ?? params.absolutePath;
|
|
5965
|
+
}
|
|
5966
|
+
function createBoundaryResolutionContext(params) {
|
|
5967
|
+
const lexicalInside = path_isPathInside(params.rootPath, params.absolutePath);
|
|
5968
|
+
const canonicalOutsideLexicalPath = resolveCanonicalOutsideLexicalPath({
|
|
5969
|
+
absolutePath: params.absolutePath,
|
|
5970
|
+
outsideLexicalCanonicalPath: params.outsideLexicalCanonicalPath,
|
|
5971
|
+
});
|
|
5972
|
+
assertLexicalBoundaryOrCanonicalAlias({
|
|
5973
|
+
skipLexicalRootCheck: params.resolveParams.skipLexicalRootCheck,
|
|
5974
|
+
lexicalInside,
|
|
5975
|
+
canonicalOutsideLexicalPath,
|
|
5976
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
5977
|
+
boundaryLabel: params.resolveParams.boundaryLabel,
|
|
5978
|
+
rootPath: params.rootPath,
|
|
5979
|
+
absolutePath: params.absolutePath,
|
|
5980
|
+
});
|
|
5981
|
+
return {
|
|
5982
|
+
rootPath: params.rootPath,
|
|
5983
|
+
absolutePath: params.absolutePath,
|
|
5984
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
5985
|
+
lexicalInside,
|
|
5986
|
+
canonicalOutsideLexicalPath,
|
|
5987
|
+
};
|
|
5988
|
+
}
|
|
5989
|
+
async function resolveOutsideRootPathAsync(params) {
|
|
5990
|
+
if (params.context.lexicalInside) {
|
|
5991
|
+
return null;
|
|
5657
5992
|
}
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5993
|
+
const kind = await getPathKind(params.context.absolutePath, false);
|
|
5994
|
+
return buildOutsideRootPathFromContext({
|
|
5995
|
+
boundaryLabel: params.boundaryLabel,
|
|
5996
|
+
context: params.context,
|
|
5997
|
+
kind,
|
|
5998
|
+
});
|
|
5999
|
+
}
|
|
6000
|
+
function resolveOutsideRootPathSync(params) {
|
|
6001
|
+
if (params.context.lexicalInside) {
|
|
6002
|
+
return null;
|
|
5662
6003
|
}
|
|
5663
|
-
|
|
5664
|
-
|
|
6004
|
+
const kind = getPathKindSync(params.context.absolutePath, false);
|
|
6005
|
+
return buildOutsideRootPathFromContext({
|
|
6006
|
+
boundaryLabel: params.boundaryLabel,
|
|
6007
|
+
context: params.context,
|
|
6008
|
+
kind,
|
|
6009
|
+
});
|
|
6010
|
+
}
|
|
6011
|
+
function buildOutsideRootPathFromContext(params) {
|
|
6012
|
+
return buildOutsideLexicalRootPath({
|
|
6013
|
+
boundaryLabel: params.boundaryLabel,
|
|
6014
|
+
rootCanonicalPath: params.context.rootCanonicalPath,
|
|
6015
|
+
absolutePath: params.context.absolutePath,
|
|
6016
|
+
canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
|
|
6017
|
+
rootPath: params.context.rootPath,
|
|
6018
|
+
kind: params.kind,
|
|
6019
|
+
});
|
|
6020
|
+
}
|
|
6021
|
+
async function resolveOutsideLexicalCanonicalPathAsync(params) {
|
|
6022
|
+
if (path_isPathInside(params.rootPath, params.absolutePath)) {
|
|
6023
|
+
return undefined;
|
|
5665
6024
|
}
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
6025
|
+
return await resolvePathViaExistingAncestor(params.absolutePath);
|
|
6026
|
+
}
|
|
6027
|
+
function resolveOutsideLexicalCanonicalPathSync(params) {
|
|
6028
|
+
if (isPathInside(params.rootPath, params.absolutePath)) {
|
|
6029
|
+
return undefined;
|
|
5670
6030
|
}
|
|
6031
|
+
return resolvePathViaExistingAncestorSync(params.absolutePath);
|
|
5671
6032
|
}
|
|
5672
|
-
function
|
|
5673
|
-
|
|
6033
|
+
function buildOutsideLexicalRootPath(params) {
|
|
6034
|
+
assertInsideBoundary({
|
|
6035
|
+
boundaryLabel: params.boundaryLabel,
|
|
6036
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
6037
|
+
candidatePath: params.canonicalOutsideLexicalPath,
|
|
6038
|
+
absolutePath: params.absolutePath,
|
|
6039
|
+
});
|
|
6040
|
+
return buildResolvedRootPath({
|
|
6041
|
+
absolutePath: params.absolutePath,
|
|
6042
|
+
canonicalPath: params.canonicalOutsideLexicalPath,
|
|
6043
|
+
rootPath: params.rootPath,
|
|
6044
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
6045
|
+
kind: params.kind,
|
|
6046
|
+
});
|
|
6047
|
+
}
|
|
6048
|
+
function assertLexicalBoundaryOrCanonicalAlias(params) {
|
|
6049
|
+
if (params.skipLexicalRootCheck || params.lexicalInside) {
|
|
5674
6050
|
return;
|
|
5675
|
-
|
|
6051
|
+
}
|
|
6052
|
+
if (path_isPathInside(params.rootCanonicalPath, params.canonicalOutsideLexicalPath)) {
|
|
6053
|
+
return;
|
|
6054
|
+
}
|
|
6055
|
+
throw pathEscapeError({
|
|
6056
|
+
boundaryLabel: params.boundaryLabel,
|
|
6057
|
+
rootPath: params.rootPath,
|
|
6058
|
+
absolutePath: params.absolutePath,
|
|
6059
|
+
});
|
|
5676
6060
|
}
|
|
5677
|
-
function
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
6061
|
+
function buildResolvedRootPath(params) {
|
|
6062
|
+
return {
|
|
6063
|
+
absolutePath: params.absolutePath,
|
|
6064
|
+
canonicalPath: params.canonicalPath,
|
|
6065
|
+
rootPath: params.rootPath,
|
|
6066
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
6067
|
+
relativePath: relativeInsideRoot(params.rootCanonicalPath, params.canonicalPath),
|
|
6068
|
+
exists: params.kind.exists,
|
|
6069
|
+
kind: params.kind.kind,
|
|
6070
|
+
};
|
|
5685
6071
|
}
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
6072
|
+
async function getPathKind(absolutePath, preserveFinalSymlink) {
|
|
6073
|
+
try {
|
|
6074
|
+
const stat = preserveFinalSymlink
|
|
6075
|
+
? await promises_.lstat(absolutePath)
|
|
6076
|
+
: await promises_.stat(absolutePath);
|
|
6077
|
+
return { exists: true, kind: toResolvedKind(stat) };
|
|
6078
|
+
}
|
|
6079
|
+
catch (error) {
|
|
6080
|
+
if (path_isNotFoundPathError(error)) {
|
|
6081
|
+
return { exists: false, kind: "missing" };
|
|
6082
|
+
}
|
|
6083
|
+
throw error;
|
|
6084
|
+
}
|
|
5697
6085
|
}
|
|
5698
|
-
function
|
|
5699
|
-
|
|
5700
|
-
|
|
6086
|
+
function getPathKindSync(absolutePath, preserveFinalSymlink) {
|
|
6087
|
+
try {
|
|
6088
|
+
const stat = preserveFinalSymlink ? fs.lstatSync(absolutePath) : fs.statSync(absolutePath);
|
|
6089
|
+
return { exists: true, kind: toResolvedKind(stat) };
|
|
6090
|
+
}
|
|
6091
|
+
catch (error) {
|
|
6092
|
+
if (isNotFoundPathError(error)) {
|
|
6093
|
+
return { exists: false, kind: "missing" };
|
|
6094
|
+
}
|
|
6095
|
+
throw error;
|
|
6096
|
+
}
|
|
5701
6097
|
}
|
|
5702
|
-
function
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
6098
|
+
function toResolvedKind(stat) {
|
|
6099
|
+
if (stat.isFile()) {
|
|
6100
|
+
return "file";
|
|
6101
|
+
}
|
|
6102
|
+
if (stat.isDirectory()) {
|
|
6103
|
+
return "directory";
|
|
6104
|
+
}
|
|
6105
|
+
if (stat.isSymbolicLink()) {
|
|
6106
|
+
return "symlink";
|
|
6107
|
+
}
|
|
6108
|
+
return "other";
|
|
5711
6109
|
}
|
|
5712
|
-
|
|
5713
|
-
const
|
|
5714
|
-
if (
|
|
5715
|
-
return
|
|
5716
|
-
try {
|
|
5717
|
-
return params.nowMs - (await promises_.stat(params.lockPath)).mtimeMs > params.staleMs;
|
|
6110
|
+
function relativeInsideRoot(rootPath, targetPath) {
|
|
6111
|
+
const relative = external_node_path_.relative(external_node_path_.resolve(rootPath), external_node_path_.resolve(targetPath));
|
|
6112
|
+
if (!relative || relative === ".") {
|
|
6113
|
+
return "";
|
|
5718
6114
|
}
|
|
5719
|
-
|
|
5720
|
-
return
|
|
6115
|
+
if (isPathRelativeEscape(relative)) {
|
|
6116
|
+
return "";
|
|
5721
6117
|
}
|
|
6118
|
+
return relative;
|
|
5722
6119
|
}
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
const constructor = globalThis.SuppressedError;
|
|
5727
|
-
if (constructor) {
|
|
5728
|
-
return new constructor(error, suppressed, message);
|
|
6120
|
+
function assertInsideBoundary(params) {
|
|
6121
|
+
if (path_isPathInside(params.rootCanonicalPath, params.candidatePath)) {
|
|
6122
|
+
return;
|
|
5729
6123
|
}
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
});
|
|
5738
|
-
return combined;
|
|
6124
|
+
throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
|
|
6125
|
+
}
|
|
6126
|
+
function pathEscapeError(params) {
|
|
6127
|
+
return new Error(`Path escapes ${params.boundaryLabel} (${shortPath(params.rootPath)}): ${shortPath(params.absolutePath)}`);
|
|
6128
|
+
}
|
|
6129
|
+
function symlinkEscapeError(params) {
|
|
6130
|
+
return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
|
|
5739
6131
|
}
|
|
5740
6132
|
|
|
5741
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-
|
|
5742
|
-
|
|
6133
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-root.js
|
|
5743
6134
|
|
|
5744
6135
|
|
|
5745
6136
|
|
|
5746
6137
|
|
|
5747
6138
|
|
|
5748
6139
|
|
|
5749
|
-
async function
|
|
5750
|
-
const resolved =
|
|
5751
|
-
const
|
|
5752
|
-
|
|
6140
|
+
async function openSidecarRoot(lockRoot, relative, discardObservation, onOpenFailure) {
|
|
6141
|
+
const resolved = await lockRoot.resolve(relative);
|
|
6142
|
+
const canonicalPath = async () => (await resolveRootPath({
|
|
6143
|
+
absolutePath: resolved,
|
|
6144
|
+
rootPath: lockRoot.rootReal,
|
|
6145
|
+
rootCanonicalPath: lockRoot.rootReal,
|
|
6146
|
+
boundaryLabel: "sidecar lock root",
|
|
6147
|
+
})).canonicalPath;
|
|
6148
|
+
const expectedRealPath = await canonicalPath();
|
|
6149
|
+
if (expectedRealPath === lockRoot.rootReal)
|
|
6150
|
+
throw new errors_FsSafeError("not-file", "sidecar lock is a directory");
|
|
6151
|
+
const parents = [];
|
|
6152
|
+
let completeParents = true;
|
|
6153
|
+
if (discardObservation) {
|
|
6154
|
+
// Follow permitted in-root aliases first; receipts cover canonical ancestry.
|
|
6155
|
+
for (let dir = external_node_path_.dirname(expectedRealPath);; dir = external_node_path_.dirname(dir)) {
|
|
6156
|
+
try {
|
|
6157
|
+
parents.push({ dir, stat: await inspectDirectoryIdentity(dir) });
|
|
6158
|
+
}
|
|
6159
|
+
catch (error) {
|
|
6160
|
+
if (!path_isNotFoundPathError(error))
|
|
6161
|
+
throw error;
|
|
6162
|
+
completeParents = false;
|
|
6163
|
+
}
|
|
6164
|
+
if (dir === lockRoot.rootReal)
|
|
6165
|
+
break;
|
|
6166
|
+
}
|
|
6167
|
+
}
|
|
6168
|
+
const observation = fileObservation();
|
|
5753
6169
|
try {
|
|
5754
|
-
|
|
6170
|
+
const opened = await observation.run(() => lockRoot.open(relative));
|
|
6171
|
+
if (opened.realPath !== expectedRealPath) {
|
|
6172
|
+
await opened.handle.close().catch(() => undefined);
|
|
6173
|
+
throw new errors_FsSafeError("path-mismatch", "sidecar lock path changed during open");
|
|
6174
|
+
}
|
|
6175
|
+
return opened;
|
|
5755
6176
|
}
|
|
5756
|
-
catch {
|
|
5757
|
-
|
|
6177
|
+
catch (error) {
|
|
6178
|
+
const openFailed = observation.has(error, `open:${resolved}`);
|
|
6179
|
+
if (openFailed)
|
|
6180
|
+
onOpenFailure?.(error);
|
|
6181
|
+
const missing = openFailed && error instanceof errors_FsSafeError && error.code === "not-found";
|
|
6182
|
+
if (missing && !discardObservation)
|
|
6183
|
+
return null;
|
|
6184
|
+
const discardable = observation.has(error, `unlinked:${resolved}`) ||
|
|
6185
|
+
(discardObservation === "changed" && observation.has(error, `changed:${resolved}`));
|
|
6186
|
+
if (!discardObservation || (!missing && (!completeParents || !discardable)))
|
|
6187
|
+
throw error;
|
|
6188
|
+
try {
|
|
6189
|
+
try {
|
|
6190
|
+
const current = await lockRoot.stat(relative);
|
|
6191
|
+
if (!current.isFile || current.isSymbolicLink || current.nlink !== 1)
|
|
6192
|
+
throw error;
|
|
6193
|
+
}
|
|
6194
|
+
catch (probeError) {
|
|
6195
|
+
if (!(probeError instanceof errors_FsSafeError && probeError.code === "not-found"))
|
|
6196
|
+
throw probeError;
|
|
6197
|
+
}
|
|
6198
|
+
for (const { dir, stat } of parents)
|
|
6199
|
+
await inspectDirectoryIdentity(dir, stat);
|
|
6200
|
+
await lockRoot.resolve(relative);
|
|
6201
|
+
if (await canonicalPath() === expectedRealPath)
|
|
6202
|
+
return null;
|
|
6203
|
+
}
|
|
6204
|
+
catch {
|
|
6205
|
+
// Keep the original failure when directory or confinement proof fails.
|
|
6206
|
+
}
|
|
6207
|
+
throw error;
|
|
5758
6208
|
}
|
|
5759
6209
|
}
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
6210
|
+
|
|
6211
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
|
|
6212
|
+
let test_hooks_fsSafeTestHooks;
|
|
6213
|
+
function allowFsSafeTestHooks() {
|
|
6214
|
+
return false || process.env.VITEST === "true";
|
|
6215
|
+
}
|
|
6216
|
+
function getFsSafeTestHooks() {
|
|
6217
|
+
return test_hooks_fsSafeTestHooks;
|
|
6218
|
+
}
|
|
6219
|
+
function __setFsSafeTestHooksForTest(hooks) {
|
|
6220
|
+
if (hooks && !allowFsSafeTestHooks()) {
|
|
6221
|
+
throw new Error("__setFsSafeTestHooksForTest is only available in tests");
|
|
6222
|
+
}
|
|
6223
|
+
test_hooks_fsSafeTestHooks = hooks;
|
|
6224
|
+
}
|
|
6225
|
+
|
|
6226
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
6227
|
+
|
|
6228
|
+
|
|
6229
|
+
|
|
6230
|
+
|
|
6231
|
+
|
|
6232
|
+
|
|
6233
|
+
|
|
6234
|
+
|
|
6235
|
+
|
|
6236
|
+
|
|
6237
|
+
const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
|
|
6238
|
+
const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
|
|
6239
|
+
const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
|
|
6240
|
+
const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
|
|
6241
|
+
const SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN = new RegExp(`\\n(${SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX}[ \\t]{${SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS}})\\n$`);
|
|
6242
|
+
function parseSidecarLockSnapshot(snapshot, parser) {
|
|
6243
|
+
return snapshot && { ...snapshot, payload: parseSidecarLockPayload(snapshot.raw, parser) };
|
|
6244
|
+
}
|
|
6245
|
+
function createSidecarLockOwnershipToken() {
|
|
6246
|
+
let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
|
|
6247
|
+
for (const byte of (0,external_node_crypto_.randomBytes)(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
|
|
6248
|
+
for (let bit = 7; bit >= 0; bit -= 1) {
|
|
6249
|
+
token += byte & (1 << bit) ? "\t" : " ";
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
6252
|
+
return token;
|
|
6253
|
+
}
|
|
6254
|
+
function readSidecarLockOwnershipToken(raw) {
|
|
6255
|
+
return SIDECAR_LOCK_OWNERSHIP_TOKEN_PATTERN.exec(raw)?.[1];
|
|
6256
|
+
}
|
|
6257
|
+
function serializeSidecarLockPayload(payload) {
|
|
6258
|
+
const ownershipToken = createSidecarLockOwnershipToken();
|
|
6259
|
+
return {
|
|
6260
|
+
raw: `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`,
|
|
6261
|
+
ownershipToken,
|
|
6262
|
+
};
|
|
6263
|
+
}
|
|
6264
|
+
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
6265
|
+
const resolved = external_node_path_.resolve(lockPath);
|
|
6266
|
+
const lexicalRelative = external_node_path_.relative(lockRoot.rootDir, resolved);
|
|
6267
|
+
const relative = lexicalRelative !== ".." &&
|
|
6268
|
+
!lexicalRelative.startsWith(`..${external_node_path_.sep}`) &&
|
|
6269
|
+
!external_node_path_.isAbsolute(lexicalRelative)
|
|
6270
|
+
? lexicalRelative
|
|
6271
|
+
: external_node_path_.relative(lockRoot.rootReal, resolved);
|
|
6272
|
+
if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relative)) {
|
|
6273
|
+
throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
6274
|
+
}
|
|
6275
|
+
return relative.split(external_node_path_.sep).join(external_node_path_.posix.sep);
|
|
6276
|
+
}
|
|
6277
|
+
function parseSidecarLockPayload(raw, parser) {
|
|
6278
|
+
if (parser) {
|
|
6279
|
+
return parser(raw);
|
|
6280
|
+
}
|
|
6281
|
+
try {
|
|
6282
|
+
const parsed = JSON.parse(raw);
|
|
6283
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
6284
|
+
}
|
|
6285
|
+
catch {
|
|
6286
|
+
return null;
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6289
|
+
function missingSnapshotPath(error) {
|
|
6290
|
+
if (error.code === "ENOENT")
|
|
6291
|
+
return null;
|
|
6292
|
+
throw error;
|
|
6293
|
+
}
|
|
6294
|
+
async function readSidecarLockSnapshot(lockPath, options = {}) {
|
|
6295
|
+
return parseSidecarLockSnapshot(await readSidecarLockRawSnapshot(lockPath, options), options.parsePayload);
|
|
6296
|
+
}
|
|
6297
|
+
async function readSidecarLockRawSnapshot(lockPath, options = {}) {
|
|
6298
|
+
let handle;
|
|
6299
|
+
try {
|
|
6300
|
+
if (options.lockRoot) {
|
|
6301
|
+
const lockRoot = options.lockRoot;
|
|
6302
|
+
const relative = relativeSidecarLockPath(lockRoot, lockPath);
|
|
6303
|
+
const opened = await openSidecarRoot(lockRoot, relative, options.discardObservation, options.onOpenFailure);
|
|
6304
|
+
if (!opened)
|
|
6305
|
+
return null;
|
|
6306
|
+
try {
|
|
6307
|
+
const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
|
|
6308
|
+
return {
|
|
6309
|
+
raw,
|
|
6310
|
+
stat: opened.stat,
|
|
6311
|
+
};
|
|
6312
|
+
}
|
|
6313
|
+
finally {
|
|
6314
|
+
await opened.handle.close().catch(() => undefined);
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
const before = await promises_.lstat(lockPath).catch(missingSnapshotPath);
|
|
6318
|
+
if (!before)
|
|
6319
|
+
return null;
|
|
6320
|
+
if (!before.isFile() || before.isSymbolicLink()) {
|
|
6321
|
+
if (options.rejectNonFile) {
|
|
6322
|
+
throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
6323
|
+
}
|
|
6324
|
+
return null;
|
|
6325
|
+
}
|
|
6326
|
+
await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
|
|
6327
|
+
const noFollow = process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
|
|
6328
|
+
? external_node_fs_.constants.O_NOFOLLOW
|
|
6329
|
+
: 0;
|
|
6330
|
+
try {
|
|
6331
|
+
handle = await promises_.open(lockPath, external_node_fs_.constants.O_RDONLY |
|
|
6332
|
+
noFollow |
|
|
6333
|
+
(typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0));
|
|
6334
|
+
}
|
|
6335
|
+
catch (error) {
|
|
6336
|
+
if (error.code === "ENOENT")
|
|
6337
|
+
return null;
|
|
6338
|
+
if (options.rejectNonFile && error.code === "ELOOP") {
|
|
6339
|
+
throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
|
|
6340
|
+
cause: error,
|
|
6341
|
+
});
|
|
6342
|
+
}
|
|
6343
|
+
options.onOpenFailure?.(error);
|
|
6344
|
+
throw error;
|
|
6345
|
+
}
|
|
6346
|
+
const opened = await handle.stat();
|
|
6347
|
+
if (!opened.isFile()) {
|
|
6348
|
+
if (options.rejectNonFile) {
|
|
6349
|
+
throw new errors_FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
6350
|
+
}
|
|
6351
|
+
return null;
|
|
6352
|
+
}
|
|
6353
|
+
if (!options.allowDescriptorIdentityDrift && !file_identity_sameFileIdentity(before, opened))
|
|
6354
|
+
return null;
|
|
6355
|
+
const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
|
|
6356
|
+
const after = await promises_.lstat(lockPath).catch(missingSnapshotPath);
|
|
6357
|
+
if (!after || !after.isFile() || !file_identity_sameFileIdentity(before, after))
|
|
6358
|
+
return null;
|
|
6359
|
+
return { raw, stat: after };
|
|
6360
|
+
}
|
|
6361
|
+
finally {
|
|
6362
|
+
await handle?.close().catch(() => undefined);
|
|
6363
|
+
}
|
|
6364
|
+
}
|
|
6365
|
+
function lstatSidecarLockSync(lockPath) {
|
|
6366
|
+
try {
|
|
6367
|
+
return fsSync.lstatSync(lockPath);
|
|
6368
|
+
}
|
|
6369
|
+
catch (error) {
|
|
6370
|
+
return missingSnapshotPath(error);
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
|
|
6374
|
+
return parseSidecarLockSnapshot(readSidecarLockRawSnapshotSync(lockPath, options), parsePayload);
|
|
6375
|
+
}
|
|
6376
|
+
function readSidecarLockRawSnapshotSync(lockPath, options = {}) {
|
|
6377
|
+
let fd;
|
|
6378
|
+
try {
|
|
6379
|
+
const before = lstatSidecarLockSync(lockPath);
|
|
6380
|
+
if (!before)
|
|
6381
|
+
return null;
|
|
6382
|
+
if (!before.isFile() || before.isSymbolicLink()) {
|
|
6383
|
+
if (options.rejectNonFile) {
|
|
6384
|
+
throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
6385
|
+
}
|
|
6386
|
+
return null;
|
|
6387
|
+
}
|
|
6388
|
+
try {
|
|
6389
|
+
fd = fsSync.openSync(lockPath, resolveReadOpenFlags());
|
|
6390
|
+
}
|
|
6391
|
+
catch (error) {
|
|
6392
|
+
if (error.code === "ENOENT")
|
|
6393
|
+
return null;
|
|
6394
|
+
options.onOpenFailure?.(error);
|
|
6395
|
+
throw error;
|
|
6396
|
+
}
|
|
6397
|
+
const opened = fsSync.fstatSync(fd);
|
|
6398
|
+
if (!opened.isFile()) {
|
|
6399
|
+
if (options.rejectNonFile) {
|
|
6400
|
+
throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
6401
|
+
}
|
|
6402
|
+
return null;
|
|
6403
|
+
}
|
|
6404
|
+
const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
|
|
6405
|
+
const after = lstatSidecarLockSync(lockPath);
|
|
6406
|
+
if (!after || !sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
|
|
6407
|
+
return null;
|
|
6408
|
+
return {
|
|
6409
|
+
raw,
|
|
6410
|
+
stat: after,
|
|
6411
|
+
ownershipToken: readSidecarLockOwnershipToken(raw),
|
|
6412
|
+
};
|
|
6413
|
+
}
|
|
6414
|
+
finally {
|
|
6415
|
+
if (fd !== undefined)
|
|
6416
|
+
fsSync.closeSync(fd);
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
function removeSidecarLockIfUnchangedSync(lockPath, observed) {
|
|
6420
|
+
const current = readSidecarLockSnapshotSync(lockPath);
|
|
6421
|
+
if (!current || !sidecarLockSnapshotMatches(current, observed))
|
|
6422
|
+
return false;
|
|
6423
|
+
fsSync.rmSync(lockPath);
|
|
6424
|
+
return true;
|
|
6425
|
+
}
|
|
6426
|
+
function sidecarLockSnapshotMatches(current, observed) {
|
|
6427
|
+
if (observed.ownershipToken !== undefined) {
|
|
6428
|
+
return (current.stat?.isFile() === true &&
|
|
6429
|
+
current.raw !== undefined &&
|
|
6430
|
+
observed.raw !== undefined &&
|
|
6431
|
+
readSidecarLockOwnershipToken(current.raw) === observed.ownershipToken &&
|
|
6432
|
+
readSidecarLockOwnershipToken(observed.raw) === observed.ownershipToken &&
|
|
6433
|
+
current.raw === observed.raw);
|
|
6434
|
+
}
|
|
6435
|
+
if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
|
|
6436
|
+
return false;
|
|
6437
|
+
}
|
|
6438
|
+
if (observed.raw !== undefined) {
|
|
6439
|
+
return current.raw === observed.raw;
|
|
6440
|
+
}
|
|
6441
|
+
return observed.stat !== undefined && current.stat !== undefined;
|
|
6442
|
+
}
|
|
6443
|
+
async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
|
|
6444
|
+
const current = await readSidecarLockSnapshot(lockPath, {
|
|
6445
|
+
...options,
|
|
6446
|
+
allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
|
|
6447
|
+
});
|
|
6448
|
+
if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
|
|
6449
|
+
return false;
|
|
6450
|
+
}
|
|
6451
|
+
if (options.lockRoot) {
|
|
6452
|
+
await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath));
|
|
6453
|
+
}
|
|
6454
|
+
else {
|
|
6455
|
+
await promises_.rm(lockPath, { force: true });
|
|
6456
|
+
}
|
|
6457
|
+
return true;
|
|
6458
|
+
}
|
|
6459
|
+
async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
|
|
6460
|
+
const current = await readSidecarLockSnapshot(lockPath, {
|
|
6461
|
+
...options,
|
|
6462
|
+
allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
|
|
6463
|
+
});
|
|
6464
|
+
return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
|
|
6465
|
+
}
|
|
6466
|
+
async function sidecarReclaimGuardExists(pathname) {
|
|
6467
|
+
try {
|
|
6468
|
+
await promises_.lstat(pathname);
|
|
6469
|
+
return true;
|
|
6470
|
+
}
|
|
6471
|
+
catch (err) {
|
|
6472
|
+
if (err.code === "ENOENT") {
|
|
6473
|
+
return false;
|
|
6474
|
+
}
|
|
6475
|
+
throw err;
|
|
6476
|
+
}
|
|
6477
|
+
}
|
|
6478
|
+
async function tryAcquireSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
|
|
6479
|
+
try {
|
|
6480
|
+
await promises_.mkdir(reclaimGuardPath);
|
|
6481
|
+
reclaimGuards.add(reclaimGuardPath);
|
|
6482
|
+
return true;
|
|
6483
|
+
}
|
|
6484
|
+
catch (err) {
|
|
6485
|
+
if (err.code === "EEXIST") {
|
|
6486
|
+
return false;
|
|
6487
|
+
}
|
|
6488
|
+
throw err;
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
async function releaseSidecarReclaimGuard(reclaimGuards, reclaimGuardPath) {
|
|
6492
|
+
await promises_.rmdir(reclaimGuardPath);
|
|
6493
|
+
reclaimGuards.delete(reclaimGuardPath);
|
|
6494
|
+
}
|
|
6495
|
+
async function removeStaleSidecarLockIfAllowed(params) {
|
|
6496
|
+
if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
|
|
6497
|
+
return "not-approved";
|
|
6498
|
+
}
|
|
6499
|
+
const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
|
|
6500
|
+
if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
|
|
6501
|
+
return "changed";
|
|
6502
|
+
}
|
|
6503
|
+
if (!(await params.shouldRemoveStaleLock({
|
|
6504
|
+
lockPath: params.lockPath,
|
|
6505
|
+
normalizedTargetPath: params.normalizedTargetPath,
|
|
6506
|
+
raw: params.snapshot.raw,
|
|
6507
|
+
payload: params.snapshot.payload,
|
|
6508
|
+
}))) {
|
|
6509
|
+
return "not-approved";
|
|
6510
|
+
}
|
|
6511
|
+
if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
|
|
6512
|
+
return "changed";
|
|
6513
|
+
}
|
|
6514
|
+
try {
|
|
6515
|
+
if (params.lockRoot) {
|
|
6516
|
+
await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
|
|
6517
|
+
}
|
|
6518
|
+
else {
|
|
6519
|
+
await promises_.rm(params.lockPath);
|
|
6520
|
+
}
|
|
6521
|
+
return "removed";
|
|
6522
|
+
}
|
|
6523
|
+
catch (err) {
|
|
6524
|
+
if (err.code === "ENOENT") {
|
|
6525
|
+
return "changed";
|
|
6526
|
+
}
|
|
6527
|
+
throw err;
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6530
|
+
|
|
6531
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
|
|
6532
|
+
|
|
6533
|
+
function assertFiniteNonNegative(value, label) {
|
|
6534
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
6535
|
+
throw new RangeError(`${label} must be a finite non-negative number`);
|
|
6536
|
+
}
|
|
6537
|
+
}
|
|
6538
|
+
function validateSidecarLockRetryOptions(retry) {
|
|
6539
|
+
if (retry.retries !== undefined && (!Number.isSafeInteger(retry.retries) || retry.retries < 0)) {
|
|
6540
|
+
throw new RangeError("lock retry.retries must be a non-negative safe integer");
|
|
6541
|
+
}
|
|
6542
|
+
if (retry.factor !== undefined)
|
|
6543
|
+
assertFiniteNonNegative(retry.factor, "lock retry.factor");
|
|
6544
|
+
if (retry.minTimeout !== undefined) {
|
|
6545
|
+
assertFiniteNonNegative(retry.minTimeout, "lock retry.minTimeout");
|
|
6546
|
+
}
|
|
6547
|
+
if (retry.maxTimeout !== undefined) {
|
|
6548
|
+
assertFiniteNonNegative(retry.maxTimeout, "lock retry.maxTimeout");
|
|
6549
|
+
}
|
|
6550
|
+
if (retry.minTimeout !== undefined &&
|
|
6551
|
+
retry.maxTimeout !== undefined &&
|
|
6552
|
+
retry.minTimeout > retry.maxTimeout) {
|
|
6553
|
+
throw new RangeError("lock retry.minTimeout must not exceed retry.maxTimeout");
|
|
6554
|
+
}
|
|
6555
|
+
}
|
|
6556
|
+
function validateSidecarLockTimeoutMs(timeoutMs) {
|
|
6557
|
+
if (timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY)
|
|
6558
|
+
return;
|
|
6559
|
+
assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
|
|
6560
|
+
}
|
|
6561
|
+
function computeSidecarLockDelayMs(retry, attempt) {
|
|
6562
|
+
validateSidecarLockRetryOptions(retry);
|
|
6563
|
+
const minTimeout = retry.minTimeout ?? 50;
|
|
6564
|
+
const maxTimeout = retry.maxTimeout ?? 1000;
|
|
6565
|
+
const factor = retry.factor ?? 1;
|
|
6566
|
+
const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
|
|
6567
|
+
const jitter = retry.randomize ? 1 + Math.random() : 1;
|
|
6568
|
+
return Math.min(maxTimeout, Math.round(base * jitter));
|
|
6569
|
+
}
|
|
6570
|
+
// Windows denies access to a lock file while a just-unlinked directory entry
|
|
6571
|
+
// is still being torn down, so a contended acquire sees EPERM on a name that is
|
|
6572
|
+
// already gone -- both when creating it exclusively and when reading the
|
|
6573
|
+
// holder's snapshot. The next attempt succeeds, so this is contention rather
|
|
6574
|
+
// than a permission failure. The error must name the lock file itself: the
|
|
6575
|
+
// exclusive-create helper opens the parent directory first, and a denial from
|
|
6576
|
+
// that setup step carries no teardown evidence and has to reach the caller.
|
|
6577
|
+
const maxTransientLockDenials = 8;
|
|
6578
|
+
function isTransientLockFileDenial(error, lockPath) {
|
|
6579
|
+
const denial = error;
|
|
6580
|
+
return process.platform === "win32" && denial?.code === "EPERM" && denial.path === lockPath;
|
|
6581
|
+
}
|
|
6582
|
+
function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
|
|
6583
|
+
const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
|
|
6584
|
+
return createdAtMs !== null && nowMs - createdAtMs > staleMs;
|
|
6585
|
+
}
|
|
6586
|
+
function sidecarLockPayloadCreatedAtMs(payload) {
|
|
6587
|
+
const createdAt = payload &&
|
|
6588
|
+
typeof payload === "object" &&
|
|
6589
|
+
"createdAt" in payload &&
|
|
6590
|
+
typeof payload.createdAt === "string"
|
|
6591
|
+
? payload.createdAt
|
|
6592
|
+
: "";
|
|
6593
|
+
const createdAtMs = Date.parse(createdAt);
|
|
6594
|
+
return Number.isFinite(createdAtMs) ? createdAtMs : null;
|
|
6595
|
+
}
|
|
6596
|
+
async function defaultSidecarLockShouldReclaim(params) {
|
|
6597
|
+
const createdAtMs = sidecarLockPayloadCreatedAtMs(params.payload);
|
|
6598
|
+
if (createdAtMs !== null)
|
|
6599
|
+
return params.nowMs - createdAtMs > params.staleMs;
|
|
6600
|
+
try {
|
|
6601
|
+
return params.nowMs - (await promises_.stat(params.lockPath)).mtimeMs > params.staleMs;
|
|
6602
|
+
}
|
|
6603
|
+
catch {
|
|
6604
|
+
return true;
|
|
6605
|
+
}
|
|
6606
|
+
}
|
|
6607
|
+
|
|
6608
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/suppressed-error.js
|
|
6609
|
+
function createSuppressedError(error, suppressed, message) {
|
|
6610
|
+
const constructor = globalThis.SuppressedError;
|
|
6611
|
+
if (constructor) {
|
|
6612
|
+
return new constructor(error, suppressed, message);
|
|
6613
|
+
}
|
|
6614
|
+
const combined = new Error(message, {
|
|
6615
|
+
cause: error instanceof Error ? error : undefined,
|
|
6616
|
+
});
|
|
6617
|
+
combined.name = "SuppressedError";
|
|
6618
|
+
Object.defineProperties(combined, {
|
|
6619
|
+
error: { configurable: true, value: error },
|
|
6620
|
+
suppressed: { configurable: true, value: suppressed },
|
|
6621
|
+
});
|
|
6622
|
+
return combined;
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6625
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js
|
|
6626
|
+
|
|
6627
|
+
|
|
6628
|
+
|
|
6629
|
+
|
|
6630
|
+
|
|
6631
|
+
|
|
6632
|
+
|
|
6633
|
+
|
|
6634
|
+
|
|
6635
|
+
async function resolveNormalizedTargetPath(targetPath) {
|
|
6636
|
+
const resolved = external_node_path_.resolve(targetPath);
|
|
6637
|
+
const dir = external_node_path_.dirname(resolved);
|
|
6638
|
+
await promises_.mkdir(dir, { recursive: true });
|
|
6639
|
+
try {
|
|
6640
|
+
return external_node_path_.join(await promises_.realpath(dir), external_node_path_.basename(resolved));
|
|
6641
|
+
}
|
|
6642
|
+
catch {
|
|
6643
|
+
return resolved;
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
async function acquireSidecarLock(options, context) {
|
|
6647
|
+
const retry = options.retry ?? {};
|
|
6648
|
+
validateSidecarLockRetryOptions(retry);
|
|
6649
|
+
validateSidecarLockTimeoutMs(options.timeoutMs);
|
|
6650
|
+
context.ensureExitCleanupRegistered();
|
|
6651
|
+
const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
|
|
6652
|
+
const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
|
|
6653
|
+
let held = context.held.get(normalizedTargetPath);
|
|
6654
|
+
if (held &&
|
|
5769
6655
|
options.reentrantOwner !== undefined &&
|
|
5770
6656
|
held.reentrantOwner !== undefined &&
|
|
5771
6657
|
options.reentrantOwner === held.reentrantOwner) {
|
|
@@ -5834,13 +6720,14 @@ async function acquireSidecarLock(options, context) {
|
|
|
5834
6720
|
let handle = null;
|
|
5835
6721
|
let createdSnapshot = null;
|
|
5836
6722
|
let lockFileCreateDenied = false;
|
|
6723
|
+
const payload = await options.payload();
|
|
6724
|
+
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
5837
6725
|
try {
|
|
5838
|
-
const payload = await options.payload();
|
|
5839
|
-
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
5840
6726
|
if (options.lockRoot) {
|
|
5841
|
-
const
|
|
6727
|
+
const lockRoot = options.lockRoot;
|
|
6728
|
+
const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
|
|
5842
6729
|
try {
|
|
5843
|
-
await
|
|
6730
|
+
await lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
|
|
5844
6731
|
}
|
|
5845
6732
|
catch (error) {
|
|
5846
6733
|
if (error instanceof errors_FsSafeError && error.code === "already-exists") {
|
|
@@ -5849,7 +6736,23 @@ async function acquireSidecarLock(options, context) {
|
|
|
5849
6736
|
throw error;
|
|
5850
6737
|
}
|
|
5851
6738
|
createdSnapshot = { raw, payload, ownershipToken };
|
|
5852
|
-
|
|
6739
|
+
const opened = await openSidecarRoot(lockRoot, relativeLockPath, "unlinked");
|
|
6740
|
+
if (!opened) {
|
|
6741
|
+
await waitForRetry();
|
|
6742
|
+
continue;
|
|
6743
|
+
}
|
|
6744
|
+
try {
|
|
6745
|
+
// Root.open admits the current file, not necessarily the one we created.
|
|
6746
|
+
const currentRaw = await readFileHandleBounded(opened.handle, Buffer.byteLength(raw));
|
|
6747
|
+
if (!currentRaw.equals(Buffer.from(raw))) {
|
|
6748
|
+
throw new errors_FsSafeError("path-mismatch", "created sidecar lock changed before admission");
|
|
6749
|
+
}
|
|
6750
|
+
}
|
|
6751
|
+
catch (error) {
|
|
6752
|
+
await opened.handle.close().catch(() => undefined);
|
|
6753
|
+
throw error;
|
|
6754
|
+
}
|
|
6755
|
+
handle = opened.handle;
|
|
5853
6756
|
}
|
|
5854
6757
|
else {
|
|
5855
6758
|
try {
|
|
@@ -5864,6 +6767,12 @@ async function acquireSidecarLock(options, context) {
|
|
|
5864
6767
|
await handle.writeFile(raw, "utf8");
|
|
5865
6768
|
}
|
|
5866
6769
|
const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
|
|
6770
|
+
if (snapshot.stat.nlink === 0) {
|
|
6771
|
+
await handle.close();
|
|
6772
|
+
handle = null;
|
|
6773
|
+
await waitForRetry();
|
|
6774
|
+
continue;
|
|
6775
|
+
}
|
|
5867
6776
|
const createdHeld = {
|
|
5868
6777
|
refCount: 1,
|
|
5869
6778
|
reentrantOwner: options.reentrantOwner,
|
|
@@ -5908,7 +6817,7 @@ async function acquireSidecarLock(options, context) {
|
|
|
5908
6817
|
catch (err) {
|
|
5909
6818
|
try {
|
|
5910
6819
|
if (handle) {
|
|
5911
|
-
const failedSnapshot = { payload: null };
|
|
6820
|
+
const failedSnapshot = createdSnapshot ?? { payload: null };
|
|
5912
6821
|
try {
|
|
5913
6822
|
failedSnapshot.stat = await handle.stat();
|
|
5914
6823
|
}
|
|
@@ -5920,8 +6829,8 @@ async function acquireSidecarLock(options, context) {
|
|
|
5920
6829
|
context.held.delete(normalizedTargetPath);
|
|
5921
6830
|
}
|
|
5922
6831
|
await handle.close().catch(() => undefined);
|
|
5923
|
-
//
|
|
5924
|
-
//
|
|
6832
|
+
// Root-created records retain the creator's byte/token receipt;
|
|
6833
|
+
// partial direct writes use the exclusive descriptor's identity.
|
|
5925
6834
|
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
|
|
5926
6835
|
lockRoot: options.lockRoot,
|
|
5927
6836
|
parsePayload: options.parsePayload,
|
|
@@ -5947,24 +6856,29 @@ async function acquireSidecarLock(options, context) {
|
|
|
5947
6856
|
if (ownsReclaimGuard) {
|
|
5948
6857
|
await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
|
|
5949
6858
|
ownsReclaimGuard = false;
|
|
6859
|
+
await waitForRetry();
|
|
5950
6860
|
continue;
|
|
5951
6861
|
}
|
|
5952
6862
|
const nowMs = Date.now();
|
|
5953
|
-
let
|
|
6863
|
+
let rawSnapshot;
|
|
6864
|
+
let lockFileOpenDenied = false;
|
|
5954
6865
|
try {
|
|
5955
|
-
|
|
6866
|
+
rawSnapshot = await readSidecarLockRawSnapshot(lockPath, {
|
|
5956
6867
|
lockRoot: options.lockRoot,
|
|
5957
|
-
parsePayload: options.parsePayload,
|
|
5958
6868
|
rejectNonFile: true,
|
|
6869
|
+
discardObservation: "changed",
|
|
6870
|
+
onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
|
|
5959
6871
|
});
|
|
5960
6872
|
}
|
|
5961
6873
|
catch (readErr) {
|
|
5962
|
-
if (!
|
|
6874
|
+
if (!lockFileOpenDenied || !withinDenialBudget())
|
|
5963
6875
|
throw readErr;
|
|
5964
6876
|
await retryOrRethrowDenial(readErr);
|
|
5965
6877
|
continue;
|
|
5966
6878
|
}
|
|
6879
|
+
const snapshot = parseSidecarLockSnapshot(rawSnapshot, options.parsePayload);
|
|
5967
6880
|
if (!snapshot) {
|
|
6881
|
+
await waitForRetry();
|
|
5968
6882
|
continue;
|
|
5969
6883
|
}
|
|
5970
6884
|
if (context.held.has(normalizedTargetPath)) {
|
|
@@ -5984,6 +6898,7 @@ async function acquireSidecarLock(options, context) {
|
|
|
5984
6898
|
lockRoot: options.lockRoot,
|
|
5985
6899
|
parsePayload: options.parsePayload,
|
|
5986
6900
|
}))) {
|
|
6901
|
+
await waitForRetry();
|
|
5987
6902
|
continue;
|
|
5988
6903
|
}
|
|
5989
6904
|
const staleRecovery = options.staleRecovery ?? "fail-closed";
|
|
@@ -6002,6 +6917,8 @@ async function acquireSidecarLock(options, context) {
|
|
|
6002
6917
|
parsePayload: options.parsePayload,
|
|
6003
6918
|
});
|
|
6004
6919
|
if (removal === "removed" || removal === "changed") {
|
|
6920
|
+
if (removal === "changed")
|
|
6921
|
+
await waitForRetry();
|
|
6005
6922
|
continue;
|
|
6006
6923
|
}
|
|
6007
6924
|
await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
|
|
@@ -6017,1176 +6934,521 @@ async function acquireSidecarLock(options, context) {
|
|
|
6017
6934
|
}
|
|
6018
6935
|
}
|
|
6019
6936
|
}
|
|
6020
|
-
finally {
|
|
6021
|
-
if (ownsReclaimGuard) {
|
|
6022
|
-
await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
|
|
6023
|
-
}
|
|
6024
|
-
}
|
|
6025
|
-
}
|
|
6026
|
-
|
|
6027
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
|
|
6028
|
-
|
|
6029
|
-
function createSidecarLockHandle(params) {
|
|
6030
|
-
let released = false;
|
|
6031
|
-
let releasePromise;
|
|
6032
|
-
let releaseStarted = false;
|
|
6033
|
-
const release = async () => {
|
|
6034
|
-
if (released)
|
|
6035
|
-
return;
|
|
6036
|
-
if (!releasePromise) {
|
|
6037
|
-
const retry = releaseStarted;
|
|
6038
|
-
releaseStarted = true;
|
|
6039
|
-
releasePromise = (async () => {
|
|
6040
|
-
await params.release({ retry });
|
|
6041
|
-
released = true;
|
|
6042
|
-
})();
|
|
6043
|
-
}
|
|
6044
|
-
try {
|
|
6045
|
-
await releasePromise;
|
|
6046
|
-
}
|
|
6047
|
-
finally {
|
|
6048
|
-
releasePromise = undefined;
|
|
6049
|
-
}
|
|
6050
|
-
};
|
|
6051
|
-
return {
|
|
6052
|
-
lockPath: params.lockPath,
|
|
6053
|
-
normalizedTargetPath: params.normalizedTargetPath,
|
|
6054
|
-
verifyStillHeld: params.verifyStillHeld,
|
|
6055
|
-
release,
|
|
6056
|
-
[Symbol.asyncDispose]: release,
|
|
6057
|
-
};
|
|
6058
|
-
}
|
|
6059
|
-
function createHeldSidecarLockHandle(params) {
|
|
6060
|
-
return createSidecarLockHandle({
|
|
6061
|
-
lockPath: params.held.lockPath,
|
|
6062
|
-
normalizedTargetPath: params.normalizedTargetPath,
|
|
6063
|
-
verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
|
|
6064
|
-
lockRoot: params.held.lockRoot,
|
|
6065
|
-
parsePayload: params.held.parsePayload,
|
|
6066
|
-
}),
|
|
6067
|
-
release: (options) => params.release(options),
|
|
6068
|
-
});
|
|
6069
|
-
}
|
|
6070
|
-
|
|
6071
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
|
|
6079
|
-
const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
|
|
6080
|
-
const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
|
|
6081
|
-
function getGlobalManagers() {
|
|
6082
|
-
const globalWithState = globalThis;
|
|
6083
|
-
if (!globalWithState[GLOBAL_STATE_KEY]) {
|
|
6084
|
-
globalWithState[GLOBAL_STATE_KEY] = new Map();
|
|
6085
|
-
}
|
|
6086
|
-
return globalWithState[GLOBAL_STATE_KEY];
|
|
6087
|
-
}
|
|
6088
|
-
function resolveManagerState(key) {
|
|
6089
|
-
const managers = getGlobalManagers();
|
|
6090
|
-
let state = managers.get(key);
|
|
6091
|
-
if (!state) {
|
|
6092
|
-
state = {
|
|
6093
|
-
cleanupRegistered: false,
|
|
6094
|
-
held: new Map(),
|
|
6095
|
-
reclaimCleanupRegistered: false,
|
|
6096
|
-
reclaimGuards: new Set(),
|
|
6097
|
-
};
|
|
6098
|
-
managers.set(key, state);
|
|
6099
|
-
}
|
|
6100
|
-
else {
|
|
6101
|
-
// The global manager symbol is shared across package copies and hot reloads.
|
|
6102
|
-
// Backfill state created by fs-safe versions that predate reclaim guards.
|
|
6103
|
-
state.reclaimCleanupRegistered ??= false;
|
|
6104
|
-
state.reclaimGuards ??= new Set();
|
|
6105
|
-
for (const held of state.held.values()) {
|
|
6106
|
-
held.refCount ??= 1;
|
|
6107
|
-
}
|
|
6108
|
-
}
|
|
6109
|
-
return state;
|
|
6110
|
-
}
|
|
6111
|
-
function snapshotMatchesSync(lockPath, observed) {
|
|
6112
|
-
let fd;
|
|
6113
|
-
try {
|
|
6114
|
-
const beforeStat = external_node_fs_.lstatSync(lockPath);
|
|
6115
|
-
if (!beforeStat.isFile()) {
|
|
6116
|
-
return false;
|
|
6117
|
-
}
|
|
6118
|
-
const openFlags = external_node_fs_.constants.O_RDONLY |
|
|
6119
|
-
(process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
|
|
6120
|
-
? external_node_fs_.constants.O_NOFOLLOW
|
|
6121
|
-
: 0) |
|
|
6122
|
-
(typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0);
|
|
6123
|
-
fd = external_node_fs_.openSync(lockPath, openFlags);
|
|
6124
|
-
const openedStat = external_node_fs_.fstatSync(fd);
|
|
6125
|
-
if (!openedStat.isFile()) {
|
|
6126
|
-
return false;
|
|
6127
|
-
}
|
|
6128
|
-
if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
|
|
6129
|
-
return false;
|
|
6130
|
-
}
|
|
6131
|
-
const raw = external_node_fs_.readFileSync(fd, "utf8");
|
|
6132
|
-
const afterStat = external_node_fs_.lstatSync(lockPath);
|
|
6133
|
-
if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
|
|
6134
|
-
return false;
|
|
6135
|
-
}
|
|
6136
|
-
return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
|
|
6137
|
-
}
|
|
6138
|
-
catch {
|
|
6139
|
-
return false;
|
|
6140
|
-
}
|
|
6141
|
-
finally {
|
|
6142
|
-
if (fd !== undefined) {
|
|
6143
|
-
try {
|
|
6144
|
-
external_node_fs_.closeSync(fd);
|
|
6145
|
-
}
|
|
6146
|
-
catch {
|
|
6147
|
-
// Best-effort process-exit cleanup.
|
|
6148
|
-
}
|
|
6149
|
-
}
|
|
6150
|
-
}
|
|
6151
|
-
}
|
|
6152
|
-
function releaseAllReclaimGuardsSync(state) {
|
|
6153
|
-
for (const reclaimGuardPath of state.reclaimGuards) {
|
|
6154
|
-
try {
|
|
6155
|
-
external_node_fs_.rmdirSync(reclaimGuardPath);
|
|
6156
|
-
state.reclaimGuards.delete(reclaimGuardPath);
|
|
6157
|
-
}
|
|
6158
|
-
catch {
|
|
6159
|
-
// Best-effort process-exit cleanup. A surviving guard fails closed.
|
|
6160
|
-
}
|
|
6161
|
-
}
|
|
6162
|
-
}
|
|
6163
|
-
function releaseAllLocksSync(state) {
|
|
6164
|
-
for (const [normalizedTargetPath, held] of state.held) {
|
|
6165
|
-
void held.handle.close().catch(() => undefined);
|
|
6166
|
-
try {
|
|
6167
|
-
if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
|
|
6168
|
-
external_node_fs_.rmSync(held.lockPath, { force: true });
|
|
6169
|
-
}
|
|
6170
|
-
}
|
|
6171
|
-
catch {
|
|
6172
|
-
// Best-effort process-exit cleanup.
|
|
6173
|
-
}
|
|
6174
|
-
state.held.delete(normalizedTargetPath);
|
|
6175
|
-
}
|
|
6176
|
-
releaseAllReclaimGuardsSync(state);
|
|
6177
|
-
}
|
|
6178
|
-
function ensureGlobalExitCleanupRegistered() {
|
|
6179
|
-
const globalWithCleanup = globalThis;
|
|
6180
|
-
if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
|
|
6181
|
-
return;
|
|
6182
|
-
globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
|
|
6183
|
-
const cleanup = () => {
|
|
6184
|
-
for (const state of getGlobalManagers().values()) {
|
|
6185
|
-
releaseAllLocksSync(state);
|
|
6186
|
-
}
|
|
6187
|
-
};
|
|
6188
|
-
globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
|
|
6189
|
-
process.on("exit", cleanup);
|
|
6190
|
-
}
|
|
6191
|
-
async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
|
|
6192
|
-
const current = state.held.get(normalizedTargetPath);
|
|
6193
|
-
if (current !== held) {
|
|
6194
|
-
return false;
|
|
6195
|
-
}
|
|
6196
|
-
if (held.releasePromise) {
|
|
6197
|
-
await held.releasePromise;
|
|
6198
|
-
return true;
|
|
6199
|
-
}
|
|
6200
|
-
if (options.force) {
|
|
6201
|
-
held.refCount = 0;
|
|
6202
|
-
}
|
|
6203
|
-
else if (!options.retry && held.refCount > 0) {
|
|
6204
|
-
held.refCount -= 1;
|
|
6205
|
-
}
|
|
6206
|
-
if (held.refCount > 0) {
|
|
6207
|
-
return false;
|
|
6208
|
-
}
|
|
6209
|
-
held.releasePromise = (async () => {
|
|
6210
|
-
await held.handle.close().catch(() => undefined);
|
|
6211
|
-
await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
|
|
6212
|
-
lockRoot: held.lockRoot,
|
|
6213
|
-
parsePayload: held.parsePayload,
|
|
6214
|
-
});
|
|
6215
|
-
if (state.held.get(normalizedTargetPath) === held) {
|
|
6216
|
-
state.held.delete(normalizedTargetPath);
|
|
6217
|
-
}
|
|
6218
|
-
if (held.compromiseTimer) {
|
|
6219
|
-
clearInterval(held.compromiseTimer);
|
|
6220
|
-
held.compromiseTimer = undefined;
|
|
6221
|
-
}
|
|
6222
|
-
})();
|
|
6223
|
-
try {
|
|
6224
|
-
await held.releasePromise;
|
|
6225
|
-
return true;
|
|
6226
|
-
}
|
|
6227
|
-
finally {
|
|
6228
|
-
held.releasePromise = undefined;
|
|
6229
|
-
}
|
|
6230
|
-
}
|
|
6231
|
-
function handleForHeldLock(state, normalizedTargetPath, held) {
|
|
6232
|
-
return createHeldSidecarLockHandle({
|
|
6233
|
-
normalizedTargetPath,
|
|
6234
|
-
held,
|
|
6235
|
-
release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
|
|
6236
|
-
});
|
|
6237
|
-
}
|
|
6238
|
-
function createSidecarLockManager(key) {
|
|
6239
|
-
const state = resolveManagerState(key);
|
|
6240
|
-
function ensureExitCleanupRegistered() {
|
|
6241
|
-
state.cleanupRegistered = true;
|
|
6242
|
-
state.reclaimCleanupRegistered = true;
|
|
6243
|
-
ensureGlobalExitCleanupRegistered();
|
|
6244
|
-
}
|
|
6245
|
-
async function acquire(options) {
|
|
6246
|
-
return await acquireSidecarLock(options, {
|
|
6247
|
-
held: state.held,
|
|
6248
|
-
reclaimGuards: state.reclaimGuards,
|
|
6249
|
-
ensureExitCleanupRegistered,
|
|
6250
|
-
handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
|
|
6251
|
-
releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
|
|
6252
|
-
});
|
|
6253
|
-
}
|
|
6254
|
-
async function withLock(options, fn) {
|
|
6255
|
-
const lock = await acquire(options);
|
|
6256
|
-
let result;
|
|
6257
|
-
try {
|
|
6258
|
-
result = await fn();
|
|
6259
|
-
}
|
|
6260
|
-
catch (bodyError) {
|
|
6261
|
-
try {
|
|
6262
|
-
await lock.release();
|
|
6263
|
-
}
|
|
6264
|
-
catch (releaseError) {
|
|
6265
|
-
throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
|
|
6266
|
-
}
|
|
6267
|
-
throw bodyError;
|
|
6268
|
-
}
|
|
6269
|
-
await lock.release();
|
|
6270
|
-
return result;
|
|
6271
|
-
}
|
|
6272
|
-
async function drain() {
|
|
6273
|
-
for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
|
|
6274
|
-
await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
|
|
6275
|
-
}
|
|
6276
|
-
}
|
|
6277
|
-
function reset() {
|
|
6278
|
-
releaseAllLocksSync(state);
|
|
6279
|
-
}
|
|
6280
|
-
function heldEntries() {
|
|
6281
|
-
return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
|
|
6282
|
-
normalizedTargetPath,
|
|
6283
|
-
lockPath: held.lockPath,
|
|
6284
|
-
acquiredAt: held.acquiredAt,
|
|
6285
|
-
metadata: held.metadata,
|
|
6286
|
-
forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
|
|
6287
|
-
}));
|
|
6288
|
-
}
|
|
6289
|
-
return { acquire, withLock, drain, reset, heldEntries };
|
|
6290
|
-
}
|
|
6291
|
-
async function withSidecarLock(targetPath, options, fn) {
|
|
6292
|
-
const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
|
|
6293
|
-
const { managerKey: _managerKey, ...acquireOptions } = options;
|
|
6294
|
-
return await manager.withLock({ ...acquireOptions, targetPath }, fn);
|
|
6295
|
-
}
|
|
6296
|
-
|
|
6297
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
function pinned_write_byteLength(input, encoding) {
|
|
6316
|
-
return typeof input === "string"
|
|
6317
|
-
? Buffer.byteLength(input, encoding ?? "utf8")
|
|
6318
|
-
: input.byteLength;
|
|
6319
|
-
}
|
|
6320
|
-
function assertSafeBasename(basename) {
|
|
6321
|
-
if (!basename ||
|
|
6322
|
-
basename === "." ||
|
|
6323
|
-
basename === ".." ||
|
|
6324
|
-
basename.includes("/") ||
|
|
6325
|
-
basename.includes("\0")) {
|
|
6326
|
-
throw new errors_FsSafeError("invalid-path", "invalid target path");
|
|
6327
|
-
}
|
|
6328
|
-
}
|
|
6329
|
-
function assertWithinMaxBytes(bytes, maxBytes) {
|
|
6330
|
-
if (maxBytes !== undefined && bytes > maxBytes) {
|
|
6331
|
-
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
6332
|
-
}
|
|
6333
|
-
}
|
|
6334
|
-
async function syncFileBestEffort(handle) {
|
|
6335
|
-
try {
|
|
6336
|
-
await handle.sync();
|
|
6337
|
-
}
|
|
6338
|
-
catch (error) {
|
|
6339
|
-
if (error?.code !== "EPERM") {
|
|
6340
|
-
throw error;
|
|
6341
|
-
}
|
|
6342
|
-
}
|
|
6343
|
-
}
|
|
6344
|
-
async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
6345
|
-
let bytes = 0;
|
|
6346
|
-
for await (const chunk of stream) {
|
|
6347
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
6348
|
-
bytes += buffer.byteLength;
|
|
6349
|
-
assertWithinMaxBytes(bytes, maxBytes);
|
|
6350
|
-
let offset = 0;
|
|
6351
|
-
while (offset < buffer.byteLength) {
|
|
6352
|
-
const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
|
|
6353
|
-
if (bytesWritten <= 0) {
|
|
6354
|
-
throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
|
|
6355
|
-
}
|
|
6356
|
-
offset += bytesWritten;
|
|
6357
|
-
}
|
|
6358
|
-
}
|
|
6359
|
-
}
|
|
6360
|
-
async function runPinnedWriteHelper(params) {
|
|
6361
|
-
const normalizedParams = { ...params, maxBytes: byte_budget_normalizeMaxBytes(params.maxBytes) };
|
|
6362
|
-
assertSafeBasename(params.basename);
|
|
6363
|
-
validatePinnedOperationPayload({
|
|
6364
|
-
relativeParentPath: params.relativeParentPath,
|
|
6365
|
-
});
|
|
6366
|
-
// The explicit compatibility policy uses the guarded Node fallback, where
|
|
6367
|
-
// content verification can replace the strict post-rename inode check.
|
|
6368
|
-
if (normalizedParams.onRenameIdentityMismatch === "verify-content") {
|
|
6369
|
-
return await runPinnedWriteFallback(normalizedParams);
|
|
6370
|
-
}
|
|
6371
|
-
const native = getNativeBinding();
|
|
6372
|
-
if (native) {
|
|
6373
|
-
return await runPinnedWriteNative(native, normalizedParams);
|
|
6374
|
-
}
|
|
6375
|
-
return await runPinnedWriteFallback(normalizedParams);
|
|
6376
|
-
}
|
|
6377
|
-
async function runPinnedWriteWithRenamePolicy(params) {
|
|
6378
|
-
const { targetPath, renameIdentity, ...writeParams } = params;
|
|
6379
|
-
if (renameIdentity !== "verify-content-with-lock") {
|
|
6380
|
-
return await runPinnedWriteHelper(writeParams);
|
|
6381
|
-
}
|
|
6382
|
-
const relativeTargetPath = writeParams.relativeParentPath
|
|
6383
|
-
? `${writeParams.relativeParentPath}/${writeParams.basename}`
|
|
6384
|
-
: writeParams.basename;
|
|
6385
|
-
const lockPath = external_node_path_.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
|
|
6386
|
-
return await withSidecarLock(writeParams.rootPath, {
|
|
6387
|
-
managerKey: `fs-safe.write:${targetPath}`,
|
|
6388
|
-
lockPath,
|
|
6389
|
-
staleMs: 30_000,
|
|
6390
|
-
timeoutMs: 5_000,
|
|
6391
|
-
payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
|
|
6392
|
-
retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
|
|
6393
|
-
}, async () => await runPinnedWriteHelper({
|
|
6394
|
-
...writeParams,
|
|
6395
|
-
onRenameIdentityMismatch: "verify-content",
|
|
6396
|
-
}));
|
|
6397
|
-
}
|
|
6398
|
-
async function runPinnedWriteFallback(params) {
|
|
6399
|
-
let parentPath = params.relativeParentPath
|
|
6400
|
-
? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
6401
|
-
: params.rootPath;
|
|
6402
|
-
if (params.mkdir) {
|
|
6403
|
-
// mkdirPathComponentsWithGuards may resolve the final component through
|
|
6404
|
-
// an in-root symlink (e.g. a skill-bank layout). Use its returned real
|
|
6405
|
-
// path for the subsequent guard and target path so we don't re-check the
|
|
6406
|
-
// original, possibly-symlinked, lexical path and reject it outright.
|
|
6407
|
-
parentPath = await mkdirPathComponentsWithGuards({
|
|
6408
|
-
rootReal: params.rootPath,
|
|
6409
|
-
targetPath: parentPath,
|
|
6410
|
-
beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
|
|
6411
|
-
});
|
|
6412
|
-
}
|
|
6413
|
-
const parentGuard = params.mkdir
|
|
6414
|
-
? await directory_guard_createAsyncDirectoryGuard(parentPath)
|
|
6415
|
-
: await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
6416
|
-
const targetPath = external_node_path_.join(parentPath, params.basename);
|
|
6417
|
-
if (params.overwrite === false) {
|
|
6418
|
-
const handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_.open(targetPath, external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL, params.mode), {
|
|
6419
|
-
onPostGuardFailure: async (openedHandle) => {
|
|
6420
|
-
// The parent failed verification, so targetPath may now resolve
|
|
6421
|
-
// somewhere else. Close the fd, but do not clean up by path.
|
|
6422
|
-
await openedHandle.close().catch(() => undefined);
|
|
6423
|
-
},
|
|
6424
|
-
});
|
|
6425
|
-
let created = true;
|
|
6426
|
-
try {
|
|
6427
|
-
const verificationIdentity = await handle.stat({ bigint: true });
|
|
6428
|
-
await handle.chmod(params.mode);
|
|
6429
|
-
if (params.input.kind === "buffer") {
|
|
6430
|
-
assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
|
|
6431
|
-
if (typeof params.input.data === "string") {
|
|
6432
|
-
await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
|
|
6433
|
-
}
|
|
6434
|
-
else {
|
|
6435
|
-
await handle.writeFile(params.input.data);
|
|
6436
|
-
}
|
|
6437
|
-
}
|
|
6438
|
-
else {
|
|
6439
|
-
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
6440
|
-
}
|
|
6441
|
-
await syncFileBestEffort(handle);
|
|
6442
|
-
const stat = await handle.stat();
|
|
6443
|
-
await syncDirectoryBestEffort(parentPath);
|
|
6444
|
-
// Publication is complete. A failed outer check must not remove its target.
|
|
6445
|
-
created = false;
|
|
6446
|
-
await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
|
|
6447
|
-
return { dev: stat.dev, ino: stat.ino };
|
|
6448
|
-
}
|
|
6449
|
-
finally {
|
|
6450
|
-
await handle.close().catch(() => undefined);
|
|
6451
|
-
if (created) {
|
|
6452
|
-
await promises_.rm(targetPath, { force: true }).catch(() => undefined);
|
|
6453
|
-
}
|
|
6454
|
-
}
|
|
6455
|
-
}
|
|
6456
|
-
const tempPath = external_node_path_.join(parentPath, `.${params.basename}.${(0,external_node_crypto_.randomUUID)()}.fallback.tmp`);
|
|
6457
|
-
const tempFlags = external_node_fs_.constants.O_WRONLY |
|
|
6458
|
-
external_node_fs_.constants.O_CREAT |
|
|
6459
|
-
external_node_fs_.constants.O_EXCL |
|
|
6460
|
-
(process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_.constants
|
|
6461
|
-
? external_node_fs_.constants.O_NOFOLLOW
|
|
6462
|
-
: 0);
|
|
6463
|
-
let handle;
|
|
6464
|
-
let tempStat;
|
|
6465
|
-
let readHandle;
|
|
6466
|
-
let renamed = false;
|
|
6467
|
-
try {
|
|
6468
|
-
handle = await promises_.open(tempPath, tempFlags, params.mode);
|
|
6469
|
-
let verificationIdentity = await handle.stat({ bigint: true });
|
|
6470
|
-
await handle.chmod(params.mode);
|
|
6471
|
-
if (params.input.kind === "buffer") {
|
|
6472
|
-
assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
|
|
6473
|
-
if (typeof params.input.data === "string") {
|
|
6474
|
-
await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
|
|
6475
|
-
}
|
|
6476
|
-
else {
|
|
6477
|
-
await handle.writeFile(params.input.data);
|
|
6478
|
-
}
|
|
6479
|
-
}
|
|
6480
|
-
else {
|
|
6481
|
-
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
6482
|
-
}
|
|
6483
|
-
tempStat = await handle.stat();
|
|
6484
|
-
const tempPathStat = await promises_.lstat(tempPath);
|
|
6485
|
-
if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
|
|
6486
|
-
throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
|
|
6487
|
-
}
|
|
6488
|
-
const expectedTempStat = tempStat;
|
|
6489
|
-
await syncFileBestEffort(handle);
|
|
6490
|
-
let verifiedIdentity = expectedTempStat;
|
|
6491
|
-
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
6492
|
-
await promises_.rename(tempPath, targetPath);
|
|
6493
|
-
renamed = true;
|
|
6494
|
-
await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
|
|
6495
|
-
await syncDirectoryBestEffort(parentPath);
|
|
6496
|
-
const targetStat = await promises_.lstat(targetPath);
|
|
6497
|
-
if (targetStat.isSymbolicLink()) {
|
|
6498
|
-
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
6499
|
-
}
|
|
6500
|
-
if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
|
|
6501
|
-
// On filesystems like rclone FUSE, rename(2) can give the destination a
|
|
6502
|
-
// different inode from the source temp fd even with zero concurrency. The
|
|
6503
|
-
// caller must ensure mutual exclusion before passing "verify-content";
|
|
6504
|
-
// fall back to a content hash for this rename-boundary check only.
|
|
6505
|
-
if (params.onRenameIdentityMismatch !== "verify-content") {
|
|
6506
|
-
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
6507
|
-
}
|
|
6508
|
-
if (params.input.kind !== "buffer") {
|
|
6509
|
-
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
6510
|
-
}
|
|
6511
|
-
const expectedHash = sha256Hex(params.input.data, params.input.encoding);
|
|
6512
|
-
readHandle = await promises_.open(targetPath, read_open_flags_resolveReadOpenFlags());
|
|
6513
|
-
const readHandleStat = await readHandle.stat({ bigint: true });
|
|
6514
|
-
const actualHash = sha256Hex(await readHandle.readFile());
|
|
6515
|
-
if (actualHash !== expectedHash) {
|
|
6516
|
-
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
6517
|
-
}
|
|
6518
|
-
// The content-verified destination, not the old temp inode, is now pinned.
|
|
6519
|
-
verificationIdentity = readHandleStat;
|
|
6520
|
-
// Preserve the helper's legacy numeric return facts, not the private proof.
|
|
6521
|
-
verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
|
|
6522
|
-
}
|
|
6523
|
-
});
|
|
6524
|
-
await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
|
|
6525
|
-
return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
|
|
6526
|
-
}
|
|
6527
|
-
finally {
|
|
6528
|
-
await readHandle?.close().catch(() => undefined);
|
|
6529
|
-
await handle?.close().catch(() => undefined);
|
|
6530
|
-
if (!renamed) {
|
|
6531
|
-
await promises_.rm(tempPath, { force: true }).catch(() => undefined);
|
|
6532
|
-
}
|
|
6533
|
-
}
|
|
6534
|
-
}
|
|
6535
|
-
|
|
6536
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/error-detail.js
|
|
6537
|
-
const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
|
|
6538
|
-
function formatErrorDetail(value) {
|
|
6539
|
-
return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
6540
|
-
}
|
|
6541
|
-
|
|
6542
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-symlink.js
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
function normalizeSymlinkResolutionError(error) {
|
|
6550
|
-
if (isSymlinkOpenError(error)) {
|
|
6551
|
-
throw new errors_FsSafeError("symlink", "symlink path could not be resolved", {
|
|
6552
|
-
cause: error instanceof Error ? error : undefined,
|
|
6553
|
-
});
|
|
6554
|
-
}
|
|
6555
|
-
if (!path_isNotFoundPathError(error))
|
|
6556
|
-
throw error;
|
|
6557
|
-
}
|
|
6558
|
-
async function resolveSymlinkHopPath(symlinkPath) {
|
|
6559
|
-
try {
|
|
6560
|
-
return external_node_path_.resolve(await promises_.realpath(symlinkPath));
|
|
6561
|
-
}
|
|
6562
|
-
catch (error) {
|
|
6563
|
-
normalizeSymlinkResolutionError(error);
|
|
6564
|
-
const linkTarget = await promises_.readlink(symlinkPath);
|
|
6565
|
-
return resolvePathViaExistingAncestor(external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget));
|
|
6566
|
-
}
|
|
6567
|
-
}
|
|
6568
|
-
function root_path_symlink_resolveSymlinkHopPathSync(symlinkPath) {
|
|
6569
|
-
try {
|
|
6570
|
-
return path.resolve(fs.realpathSync(symlinkPath));
|
|
6571
|
-
}
|
|
6572
|
-
catch (error) {
|
|
6573
|
-
normalizeSymlinkResolutionError(error);
|
|
6574
|
-
const linkTarget = fs.readlinkSync(symlinkPath);
|
|
6575
|
-
return resolvePathViaExistingAncestorSync(path.resolve(path.dirname(symlinkPath), linkTarget));
|
|
6576
|
-
}
|
|
6577
|
-
}
|
|
6578
|
-
|
|
6579
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/safe-path-segment.js
|
|
6580
|
-
|
|
6581
|
-
const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
|
|
6582
|
-
const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
6583
|
-
// Windows treats "C:name" as relative to the drive's current directory even
|
|
6584
|
-
// though path.win32.isAbsolute() reports false.
|
|
6585
|
-
const DRIVE_RELATIVE_PREFIX = /^[A-Za-z]:(?![\\/])/;
|
|
6586
|
-
const HYPHEN_CHAR_CODE = 0x2d;
|
|
6587
|
-
function safe_path_segment_isDriveRelativePath(value) {
|
|
6588
|
-
return DRIVE_RELATIVE_PREFIX.test(value);
|
|
6589
|
-
}
|
|
6590
|
-
function assertNoDriveRelativePathSegments(value, label) {
|
|
6591
|
-
if (value.split("/").some(safe_path_segment_isDriveRelativePath)) {
|
|
6592
|
-
throw new errors_FsSafeError("invalid-path", `${label} must not contain a drive letter`);
|
|
6593
|
-
}
|
|
6594
|
-
return value;
|
|
6595
|
-
}
|
|
6596
|
-
function trimHyphenEdges(value) {
|
|
6597
|
-
let start = 0;
|
|
6598
|
-
let end = value.length;
|
|
6599
|
-
while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
|
|
6600
|
-
start += 1;
|
|
6601
|
-
}
|
|
6602
|
-
while (end > start && value.charCodeAt(end - 1) === HYPHEN_CHAR_CODE) {
|
|
6603
|
-
end -= 1;
|
|
6604
|
-
}
|
|
6605
|
-
return start === 0 && end === value.length ? value : value.slice(start, end);
|
|
6606
|
-
}
|
|
6607
|
-
function isSafePathSegment(segment, options = {}) {
|
|
6608
|
-
return (segment !== "" &&
|
|
6609
|
-
segment !== "." &&
|
|
6610
|
-
segment !== ".." &&
|
|
6611
|
-
!segment.includes("/") &&
|
|
6612
|
-
!segment.includes("\\") &&
|
|
6613
|
-
!segment.includes("\0") &&
|
|
6614
|
-
(options.allowDotPrefix === true || !segment.startsWith(".")) &&
|
|
6615
|
-
(options.allowDotPrefix === true
|
|
6616
|
-
? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
|
|
6617
|
-
: SAFE_PATH_SEGMENT_PATTERN.test(segment)));
|
|
6618
|
-
}
|
|
6619
|
-
function assertSafePathSegment(segment, options = {}) {
|
|
6620
|
-
// Validate the exact value callers will later join into paths; trimming here
|
|
6621
|
-
// would let whitespace-padded ids pass and then be used verbatim.
|
|
6622
|
-
if (!isSafePathSegment(segment, options)) {
|
|
6623
|
-
throw new FsSafeError("invalid-path", `${options.label ?? "path segment"} must be a safe path segment`);
|
|
6624
|
-
}
|
|
6625
|
-
return segment;
|
|
6626
|
-
}
|
|
6627
|
-
function sanitizeSafePathSegment(value, fallback, options = {}) {
|
|
6628
|
-
const sanitized = value
|
|
6629
|
-
.trim()
|
|
6630
|
-
.replace(/[\\/]+/g, "-")
|
|
6631
|
-
.replace(/\0/g, "")
|
|
6632
|
-
.replace(/[^A-Za-z0-9._-]+/g, "-");
|
|
6633
|
-
const trimmed = trimHyphenEdges(sanitized);
|
|
6634
|
-
if (isSafePathSegment(trimmed, options)) {
|
|
6635
|
-
return trimmed;
|
|
6636
|
-
}
|
|
6637
|
-
return assertSafePathSegment(fallback, { ...options, label: "fallback path segment" });
|
|
6638
|
-
}
|
|
6639
|
-
function assertSafePathPrefix(prefix, options = {}) {
|
|
6640
|
-
// Prefixes are often derived from safe filenames. Normalize harmless
|
|
6641
|
-
// filename characters first, but still reject real path-control bytes.
|
|
6642
|
-
if (prefix.includes("/") || prefix.includes("\\") || prefix.includes("\0")) {
|
|
6643
|
-
return assertSafePathSegment(prefix, {
|
|
6644
|
-
allowDotPrefix: true,
|
|
6645
|
-
...options,
|
|
6646
|
-
label: options.label ?? "path prefix",
|
|
6647
|
-
});
|
|
6937
|
+
finally {
|
|
6938
|
+
if (ownsReclaimGuard) {
|
|
6939
|
+
await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath).catch(() => undefined);
|
|
6940
|
+
}
|
|
6648
6941
|
}
|
|
6649
|
-
return assertSafePathSegment(prefix.replace(/[^A-Za-z0-9._-]+/g, "-"), {
|
|
6650
|
-
allowDotPrefix: true,
|
|
6651
|
-
...options,
|
|
6652
|
-
label: options.label ?? "path prefix",
|
|
6653
|
-
});
|
|
6654
6942
|
}
|
|
6655
6943
|
|
|
6656
|
-
|
|
6657
|
-
var external_node_os_ = __webpack_require__(8161);
|
|
6658
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/short-path.js
|
|
6659
|
-
|
|
6944
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
|
|
6660
6945
|
|
|
6661
|
-
function
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6946
|
+
function createSidecarLockHandle(params) {
|
|
6947
|
+
let released = false;
|
|
6948
|
+
let releasePromise;
|
|
6949
|
+
let releaseStarted = false;
|
|
6950
|
+
const release = async () => {
|
|
6951
|
+
if (released)
|
|
6952
|
+
return;
|
|
6953
|
+
if (!releasePromise) {
|
|
6954
|
+
const retry = releaseStarted;
|
|
6955
|
+
releaseStarted = true;
|
|
6956
|
+
releasePromise = (async () => {
|
|
6957
|
+
await params.release({ retry });
|
|
6958
|
+
released = true;
|
|
6959
|
+
})();
|
|
6960
|
+
}
|
|
6961
|
+
try {
|
|
6962
|
+
await releasePromise;
|
|
6963
|
+
}
|
|
6964
|
+
finally {
|
|
6965
|
+
releasePromise = undefined;
|
|
6966
|
+
}
|
|
6967
|
+
};
|
|
6968
|
+
return {
|
|
6969
|
+
lockPath: params.lockPath,
|
|
6970
|
+
normalizedTargetPath: params.normalizedTargetPath,
|
|
6971
|
+
verifyStillHeld: params.verifyStillHeld,
|
|
6972
|
+
release,
|
|
6973
|
+
[Symbol.asyncDispose]: release,
|
|
6974
|
+
};
|
|
6975
|
+
}
|
|
6976
|
+
function createHeldSidecarLockHandle(params) {
|
|
6977
|
+
return createSidecarLockHandle({
|
|
6978
|
+
lockPath: params.held.lockPath,
|
|
6979
|
+
normalizedTargetPath: params.normalizedTargetPath,
|
|
6980
|
+
verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
|
|
6981
|
+
lockRoot: params.held.lockRoot,
|
|
6982
|
+
parsePayload: params.held.parsePayload,
|
|
6983
|
+
}),
|
|
6984
|
+
release: (options) => params.release(options),
|
|
6985
|
+
});
|
|
6665
6986
|
}
|
|
6666
6987
|
|
|
6667
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6988
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
|
|
6673
6989
|
|
|
6674
6990
|
|
|
6675
6991
|
|
|
6676
6992
|
|
|
6677
6993
|
|
|
6678
6994
|
|
|
6679
|
-
const
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
allowFinalHardlinkForUnlink: true,
|
|
6687
|
-
}),
|
|
6688
|
-
};
|
|
6689
|
-
async function resolveRootPath(params) {
|
|
6690
|
-
try {
|
|
6691
|
-
return await resolveRootPathInternal(params);
|
|
6692
|
-
}
|
|
6693
|
-
catch (error) {
|
|
6694
|
-
throw sanitizeRootPathError(error);
|
|
6995
|
+
const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
|
|
6996
|
+
const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
|
|
6997
|
+
const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
|
|
6998
|
+
function getGlobalManagers() {
|
|
6999
|
+
const globalWithState = globalThis;
|
|
7000
|
+
if (!globalWithState[GLOBAL_STATE_KEY]) {
|
|
7001
|
+
globalWithState[GLOBAL_STATE_KEY] = new Map();
|
|
6695
7002
|
}
|
|
7003
|
+
return globalWithState[GLOBAL_STATE_KEY];
|
|
6696
7004
|
}
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
rootCanonicalPath,
|
|
6709
|
-
outsideLexicalCanonicalPath: await resolveOutsideLexicalCanonicalPathAsync({
|
|
6710
|
-
rootPath,
|
|
6711
|
-
absolutePath,
|
|
6712
|
-
}),
|
|
6713
|
-
});
|
|
6714
|
-
const outsideResult = await resolveOutsideRootPathAsync({
|
|
6715
|
-
boundaryLabel: params.boundaryLabel,
|
|
6716
|
-
context,
|
|
6717
|
-
});
|
|
6718
|
-
if (outsideResult) {
|
|
6719
|
-
return outsideResult;
|
|
7005
|
+
function resolveManagerState(key) {
|
|
7006
|
+
const managers = getGlobalManagers();
|
|
7007
|
+
let state = managers.get(key);
|
|
7008
|
+
if (!state) {
|
|
7009
|
+
state = {
|
|
7010
|
+
cleanupRegistered: false,
|
|
7011
|
+
held: new Map(),
|
|
7012
|
+
reclaimCleanupRegistered: false,
|
|
7013
|
+
reclaimGuards: new Set(),
|
|
7014
|
+
};
|
|
7015
|
+
managers.set(key, state);
|
|
6720
7016
|
}
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
7017
|
+
else {
|
|
7018
|
+
// The global manager symbol is shared across package copies and hot reloads.
|
|
7019
|
+
// Backfill state created by fs-safe versions that predate reclaim guards.
|
|
7020
|
+
state.reclaimCleanupRegistered ??= false;
|
|
7021
|
+
state.reclaimGuards ??= new Set();
|
|
7022
|
+
for (const held of state.held.values()) {
|
|
7023
|
+
held.refCount ??= 1;
|
|
7024
|
+
}
|
|
7025
|
+
}
|
|
7026
|
+
return state;
|
|
6727
7027
|
}
|
|
6728
|
-
function
|
|
7028
|
+
function snapshotMatchesSync(lockPath, observed) {
|
|
7029
|
+
let fd;
|
|
6729
7030
|
try {
|
|
6730
|
-
|
|
7031
|
+
const beforeStat = external_node_fs_.lstatSync(lockPath);
|
|
7032
|
+
if (!beforeStat.isFile()) {
|
|
7033
|
+
return false;
|
|
7034
|
+
}
|
|
7035
|
+
const openFlags = external_node_fs_.constants.O_RDONLY |
|
|
7036
|
+
(process.platform !== "win32" && typeof external_node_fs_.constants.O_NOFOLLOW === "number"
|
|
7037
|
+
? external_node_fs_.constants.O_NOFOLLOW
|
|
7038
|
+
: 0) |
|
|
7039
|
+
(typeof external_node_fs_.constants.O_NONBLOCK === "number" ? external_node_fs_.constants.O_NONBLOCK : 0);
|
|
7040
|
+
fd = external_node_fs_.openSync(lockPath, openFlags);
|
|
7041
|
+
const openedStat = external_node_fs_.fstatSync(fd);
|
|
7042
|
+
if (!openedStat.isFile()) {
|
|
7043
|
+
return false;
|
|
7044
|
+
}
|
|
7045
|
+
if (observed.raw !== undefined && openedStat.size !== Buffer.byteLength(observed.raw)) {
|
|
7046
|
+
return false;
|
|
7047
|
+
}
|
|
7048
|
+
const raw = external_node_fs_.readFileSync(fd, "utf8");
|
|
7049
|
+
const afterStat = external_node_fs_.lstatSync(lockPath);
|
|
7050
|
+
if (!afterStat.isFile() || !file_identity_sameFileIdentity(beforeStat, afterStat)) {
|
|
7051
|
+
return false;
|
|
7052
|
+
}
|
|
7053
|
+
return sidecarLockSnapshotMatches({ raw, payload: null, stat: afterStat }, observed);
|
|
6731
7054
|
}
|
|
6732
|
-
catch
|
|
6733
|
-
|
|
7055
|
+
catch {
|
|
7056
|
+
return false;
|
|
6734
7057
|
}
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
resolveParams: params,
|
|
6745
|
-
rootPath,
|
|
6746
|
-
absolutePath,
|
|
6747
|
-
rootCanonicalPath,
|
|
6748
|
-
outsideLexicalCanonicalPath: resolveOutsideLexicalCanonicalPathSync({
|
|
6749
|
-
rootPath,
|
|
6750
|
-
absolutePath,
|
|
6751
|
-
}),
|
|
6752
|
-
});
|
|
6753
|
-
const outsideResult = resolveOutsideRootPathSync({
|
|
6754
|
-
boundaryLabel: params.boundaryLabel,
|
|
6755
|
-
context,
|
|
6756
|
-
});
|
|
6757
|
-
if (outsideResult) {
|
|
6758
|
-
return outsideResult;
|
|
7058
|
+
finally {
|
|
7059
|
+
if (fd !== undefined) {
|
|
7060
|
+
try {
|
|
7061
|
+
external_node_fs_.closeSync(fd);
|
|
7062
|
+
}
|
|
7063
|
+
catch {
|
|
7064
|
+
// Best-effort process-exit cleanup.
|
|
7065
|
+
}
|
|
7066
|
+
}
|
|
6759
7067
|
}
|
|
6760
|
-
return resolveRootPathLexicalSync({
|
|
6761
|
-
params,
|
|
6762
|
-
absolutePath: context.absolutePath,
|
|
6763
|
-
rootPath: context.rootPath,
|
|
6764
|
-
rootCanonicalPath: context.rootCanonicalPath,
|
|
6765
|
-
});
|
|
6766
7068
|
}
|
|
6767
|
-
function
|
|
6768
|
-
|
|
6769
|
-
|
|
7069
|
+
function releaseAllReclaimGuardsSync(state) {
|
|
7070
|
+
for (const reclaimGuardPath of state.reclaimGuards) {
|
|
7071
|
+
try {
|
|
7072
|
+
external_node_fs_.rmdirSync(reclaimGuardPath);
|
|
7073
|
+
state.reclaimGuards.delete(reclaimGuardPath);
|
|
7074
|
+
}
|
|
7075
|
+
catch {
|
|
7076
|
+
// Best-effort process-exit cleanup. A surviving guard fails closed.
|
|
7077
|
+
}
|
|
6770
7078
|
}
|
|
6771
|
-
return error;
|
|
6772
7079
|
}
|
|
6773
|
-
function
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
7080
|
+
function releaseAllLocksSync(state) {
|
|
7081
|
+
for (const [normalizedTargetPath, held] of state.held) {
|
|
7082
|
+
void held.handle.close().catch(() => undefined);
|
|
7083
|
+
try {
|
|
7084
|
+
if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
|
|
7085
|
+
external_node_fs_.rmSync(held.lockPath, { force: true });
|
|
7086
|
+
}
|
|
7087
|
+
}
|
|
7088
|
+
catch {
|
|
7089
|
+
// Best-effort process-exit cleanup.
|
|
7090
|
+
}
|
|
7091
|
+
state.held.delete(normalizedTargetPath);
|
|
6781
7092
|
}
|
|
7093
|
+
releaseAllReclaimGuardsSync(state);
|
|
6782
7094
|
}
|
|
6783
|
-
function
|
|
6784
|
-
|
|
7095
|
+
function ensureGlobalExitCleanupRegistered() {
|
|
7096
|
+
const globalWithCleanup = globalThis;
|
|
7097
|
+
if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
|
|
6785
7098
|
return;
|
|
6786
|
-
|
|
6787
|
-
const
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
return Boolean(value &&
|
|
6792
|
-
(typeof value === "object" || typeof value === "function") &&
|
|
6793
|
-
"then" in value &&
|
|
6794
|
-
typeof value.then === "function");
|
|
6795
|
-
}
|
|
6796
|
-
function createLexicalTraversalState(params) {
|
|
6797
|
-
const rawAbsolutePath = params.params.absolutePath;
|
|
6798
|
-
const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
|
|
6799
|
-
const relative = rawRelativePath ?? external_node_path_.relative(params.rootPath, params.absolutePath);
|
|
6800
|
-
return {
|
|
6801
|
-
segments: splitTraversalSegments(relative),
|
|
6802
|
-
allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
|
|
6803
|
-
canonicalCursor: params.rootCanonicalPath,
|
|
6804
|
-
lexicalCursor: params.rootPath,
|
|
6805
|
-
preserveFinalSymlink: false,
|
|
6806
|
-
};
|
|
6807
|
-
}
|
|
6808
|
-
function createLexicalTraversalContext(params) {
|
|
6809
|
-
return {
|
|
6810
|
-
state: createLexicalTraversalState(params),
|
|
6811
|
-
resolveParams: params.params,
|
|
6812
|
-
rootPath: params.rootPath,
|
|
6813
|
-
rootCanonicalPath: params.rootCanonicalPath,
|
|
6814
|
-
absolutePath: params.absolutePath,
|
|
7099
|
+
globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
|
|
7100
|
+
const cleanup = () => {
|
|
7101
|
+
for (const state of getGlobalManagers().values()) {
|
|
7102
|
+
releaseAllLocksSync(state);
|
|
7103
|
+
}
|
|
6815
7104
|
};
|
|
7105
|
+
globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
|
|
7106
|
+
process.on("exit", cleanup);
|
|
6816
7107
|
}
|
|
6817
|
-
function
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
.filter((segment) => Boolean(segment) && segment !== ".");
|
|
6821
|
-
}
|
|
6822
|
-
function rawPathRelativeToRoot(rootPath, candidatePath) {
|
|
6823
|
-
if (!external_node_path_.isAbsolute(candidatePath)) {
|
|
6824
|
-
return undefined;
|
|
6825
|
-
}
|
|
6826
|
-
const root = external_node_path_.resolve(rootPath);
|
|
6827
|
-
const candidate = process.platform === "win32"
|
|
6828
|
-
? candidatePath.replaceAll("/", external_node_path_.sep)
|
|
6829
|
-
: candidatePath;
|
|
6830
|
-
if (candidate === root) {
|
|
6831
|
-
return "";
|
|
6832
|
-
}
|
|
6833
|
-
const rootWithSep = root.endsWith(external_node_path_.sep) ? root : `${root}${external_node_path_.sep}`;
|
|
6834
|
-
const candidatePrefix = candidate.slice(0, rootWithSep.length);
|
|
6835
|
-
const prefixMatches = process.platform === "win32"
|
|
6836
|
-
? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
|
|
6837
|
-
: candidatePrefix === rootWithSep;
|
|
6838
|
-
return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
|
|
6839
|
-
}
|
|
6840
|
-
function assertLexicalCursorInsideBoundary(context, candidatePath) {
|
|
6841
|
-
assertInsideBoundary({
|
|
6842
|
-
boundaryLabel: context.resolveParams.boundaryLabel,
|
|
6843
|
-
rootCanonicalPath: context.rootCanonicalPath,
|
|
6844
|
-
candidatePath,
|
|
6845
|
-
absolutePath: context.absolutePath,
|
|
6846
|
-
});
|
|
6847
|
-
}
|
|
6848
|
-
function applyMissingSuffixToCanonicalCursor(context, missingFromIndex) {
|
|
6849
|
-
const missingSuffix = context.state.segments.slice(missingFromIndex);
|
|
6850
|
-
for (const segment of missingSuffix) {
|
|
6851
|
-
advanceCanonicalCursorForSegment(context, segment);
|
|
6852
|
-
}
|
|
6853
|
-
}
|
|
6854
|
-
function advanceCanonicalCursorForSegment(context, segment) {
|
|
6855
|
-
context.state.canonicalCursor = external_node_path_.resolve(context.state.canonicalCursor, segment);
|
|
6856
|
-
assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
|
|
6857
|
-
}
|
|
6858
|
-
function finalizeLexicalResolution(context, kind) {
|
|
6859
|
-
assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
|
|
6860
|
-
return buildResolvedRootPath({
|
|
6861
|
-
absolutePath: context.absolutePath,
|
|
6862
|
-
canonicalPath: context.state.canonicalCursor,
|
|
6863
|
-
rootPath: context.rootPath,
|
|
6864
|
-
rootCanonicalPath: context.rootCanonicalPath,
|
|
6865
|
-
kind,
|
|
6866
|
-
});
|
|
6867
|
-
}
|
|
6868
|
-
function handleLexicalLstatFailure(context, error, missingFromIndex) {
|
|
6869
|
-
if (!path_isNotFoundPathError(error)) {
|
|
7108
|
+
async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
|
|
7109
|
+
const current = state.held.get(normalizedTargetPath);
|
|
7110
|
+
if (current !== held) {
|
|
6870
7111
|
return false;
|
|
6871
7112
|
}
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
function handleLexicalStatReadFailure(context, error, missingFromIndex) {
|
|
6876
|
-
if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
|
|
6877
|
-
return null;
|
|
7113
|
+
if (held.releasePromise) {
|
|
7114
|
+
await held.releasePromise;
|
|
7115
|
+
return true;
|
|
6878
7116
|
}
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
function handleLexicalStatDisposition(context, params) {
|
|
6882
|
-
if (!params.isSymbolicLink) {
|
|
6883
|
-
advanceCanonicalCursorForSegment(context, params.segment);
|
|
6884
|
-
return "continue";
|
|
7117
|
+
if (options.force) {
|
|
7118
|
+
held.refCount = 0;
|
|
6885
7119
|
}
|
|
6886
|
-
if (
|
|
6887
|
-
|
|
7120
|
+
else if (!options.retry && held.refCount > 0) {
|
|
7121
|
+
held.refCount -= 1;
|
|
6888
7122
|
}
|
|
6889
|
-
if (
|
|
6890
|
-
|
|
6891
|
-
advanceCanonicalCursorForSegment(context, params.segment);
|
|
6892
|
-
return "break";
|
|
7123
|
+
if (held.refCount > 0) {
|
|
7124
|
+
return false;
|
|
6893
7125
|
}
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
boundaryLabel: context.resolveParams.boundaryLabel,
|
|
6900
|
-
rootCanonicalPath: context.rootCanonicalPath,
|
|
6901
|
-
symlinkPath: context.state.lexicalCursor,
|
|
7126
|
+
held.releasePromise = (async () => {
|
|
7127
|
+
await held.handle.close().catch(() => undefined);
|
|
7128
|
+
await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
|
|
7129
|
+
lockRoot: held.lockRoot,
|
|
7130
|
+
parsePayload: held.parsePayload,
|
|
6902
7131
|
});
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
context.state.lexicalCursor = linkCanonical;
|
|
6906
|
-
}
|
|
6907
|
-
function readLexicalStat(context, params) {
|
|
6908
|
-
try {
|
|
6909
|
-
const stat = params.read(context.state.lexicalCursor);
|
|
6910
|
-
if (isPromiseLike(stat)) {
|
|
6911
|
-
return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
|
|
7132
|
+
if (state.held.get(normalizedTargetPath) === held) {
|
|
7133
|
+
state.held.delete(normalizedTargetPath);
|
|
6912
7134
|
}
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
7135
|
+
if (held.compromiseTimer) {
|
|
7136
|
+
clearInterval(held.compromiseTimer);
|
|
7137
|
+
held.compromiseTimer = undefined;
|
|
7138
|
+
}
|
|
7139
|
+
})();
|
|
7140
|
+
try {
|
|
7141
|
+
await held.releasePromise;
|
|
7142
|
+
return true;
|
|
6917
7143
|
}
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
|
|
6921
|
-
if (isPromiseLike(linkCanonical)) {
|
|
6922
|
-
return Promise.resolve(linkCanonical).then((value) => {
|
|
6923
|
-
applyResolvedSymlinkHop(context, value);
|
|
6924
|
-
});
|
|
7144
|
+
finally {
|
|
7145
|
+
held.releasePromise = undefined;
|
|
6925
7146
|
}
|
|
6926
|
-
applyResolvedSymlinkHop(context, linkCanonical);
|
|
6927
|
-
}
|
|
6928
|
-
function applyParentTraversalStep(context) {
|
|
6929
|
-
context.state.lexicalCursor = external_node_path_.resolve(context.state.lexicalCursor, "..");
|
|
6930
|
-
advanceCanonicalCursorForSegment(context, "..");
|
|
6931
7147
|
}
|
|
6932
|
-
function
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
}
|
|
7148
|
+
function handleForHeldLock(state, normalizedTargetPath, held) {
|
|
7149
|
+
return createHeldSidecarLockHandle({
|
|
7150
|
+
normalizedTargetPath,
|
|
7151
|
+
held,
|
|
7152
|
+
release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
|
|
7153
|
+
});
|
|
6938
7154
|
}
|
|
6939
|
-
|
|
6940
|
-
const
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
continue;
|
|
6946
|
-
}
|
|
6947
|
-
state.lexicalCursor = external_node_path_.join(state.lexicalCursor, segment);
|
|
6948
|
-
const stat = await readLexicalStat(context, {
|
|
6949
|
-
missingFromIndex: idx,
|
|
6950
|
-
read: (cursor) => promises_.lstat(cursor),
|
|
6951
|
-
});
|
|
6952
|
-
if (!stat) {
|
|
6953
|
-
break;
|
|
6954
|
-
}
|
|
6955
|
-
const disposition = handleLexicalStatDisposition(context, {
|
|
6956
|
-
isSymbolicLink: stat.isSymbolicLink(),
|
|
6957
|
-
segment,
|
|
6958
|
-
isLast,
|
|
6959
|
-
});
|
|
6960
|
-
if (disposition === "continue") {
|
|
6961
|
-
continue;
|
|
6962
|
-
}
|
|
6963
|
-
if (disposition === "break") {
|
|
6964
|
-
break;
|
|
6965
|
-
}
|
|
6966
|
-
await resolveAndApplySymlinkHop(context, {
|
|
6967
|
-
resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
|
|
6968
|
-
});
|
|
6969
|
-
if (context.resolveParams.rejectSymlinks === true) {
|
|
6970
|
-
throw new errors_FsSafeError("symlink", "symlink path component not allowed");
|
|
6971
|
-
}
|
|
7155
|
+
function createSidecarLockManager(key) {
|
|
7156
|
+
const state = resolveManagerState(key);
|
|
7157
|
+
function ensureExitCleanupRegistered() {
|
|
7158
|
+
state.cleanupRegistered = true;
|
|
7159
|
+
state.reclaimCleanupRegistered = true;
|
|
7160
|
+
ensureGlobalExitCleanupRegistered();
|
|
6972
7161
|
}
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
const segment = state.segments[idx] ?? "";
|
|
6981
|
-
const isLast = idx === state.segments.length - 1;
|
|
6982
|
-
if (segment === "..") {
|
|
6983
|
-
applyParentTraversalStep(context);
|
|
6984
|
-
continue;
|
|
6985
|
-
}
|
|
6986
|
-
state.lexicalCursor = path.join(state.lexicalCursor, segment);
|
|
6987
|
-
const maybeStat = readLexicalStat(context, {
|
|
6988
|
-
missingFromIndex: idx,
|
|
6989
|
-
read: (cursor) => fs.lstatSync(cursor),
|
|
6990
|
-
});
|
|
6991
|
-
if (isPromiseLike(maybeStat)) {
|
|
6992
|
-
throw new Error("Unexpected async lexical stat");
|
|
6993
|
-
}
|
|
6994
|
-
const stat = maybeStat;
|
|
6995
|
-
if (!stat) {
|
|
6996
|
-
break;
|
|
6997
|
-
}
|
|
6998
|
-
const disposition = handleLexicalStatDisposition(context, {
|
|
6999
|
-
isSymbolicLink: stat.isSymbolicLink(),
|
|
7000
|
-
segment,
|
|
7001
|
-
isLast,
|
|
7002
|
-
});
|
|
7003
|
-
if (disposition === "continue") {
|
|
7004
|
-
continue;
|
|
7005
|
-
}
|
|
7006
|
-
if (disposition === "break") {
|
|
7007
|
-
break;
|
|
7008
|
-
}
|
|
7009
|
-
const maybeApplied = resolveAndApplySymlinkHop(context, {
|
|
7010
|
-
resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
|
|
7162
|
+
async function acquire(options) {
|
|
7163
|
+
return await acquireSidecarLock(options, {
|
|
7164
|
+
held: state.held,
|
|
7165
|
+
reclaimGuards: state.reclaimGuards,
|
|
7166
|
+
ensureExitCleanupRegistered,
|
|
7167
|
+
handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
|
|
7168
|
+
releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
|
|
7011
7169
|
});
|
|
7012
|
-
|
|
7013
|
-
|
|
7170
|
+
}
|
|
7171
|
+
async function withLock(options, fn) {
|
|
7172
|
+
const lock = await acquire(options);
|
|
7173
|
+
let result;
|
|
7174
|
+
try {
|
|
7175
|
+
result = await fn();
|
|
7014
7176
|
}
|
|
7015
|
-
|
|
7016
|
-
|
|
7177
|
+
catch (bodyError) {
|
|
7178
|
+
try {
|
|
7179
|
+
await lock.release();
|
|
7180
|
+
}
|
|
7181
|
+
catch (releaseError) {
|
|
7182
|
+
throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
|
|
7183
|
+
}
|
|
7184
|
+
throw bodyError;
|
|
7017
7185
|
}
|
|
7186
|
+
await lock.release();
|
|
7187
|
+
return result;
|
|
7018
7188
|
}
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
}
|
|
7022
|
-
|
|
7023
|
-
return params.outsideLexicalCanonicalPath ?? params.absolutePath;
|
|
7024
|
-
}
|
|
7025
|
-
function createBoundaryResolutionContext(params) {
|
|
7026
|
-
const lexicalInside = path_isPathInside(params.rootPath, params.absolutePath);
|
|
7027
|
-
const canonicalOutsideLexicalPath = resolveCanonicalOutsideLexicalPath({
|
|
7028
|
-
absolutePath: params.absolutePath,
|
|
7029
|
-
outsideLexicalCanonicalPath: params.outsideLexicalCanonicalPath,
|
|
7030
|
-
});
|
|
7031
|
-
assertLexicalBoundaryOrCanonicalAlias({
|
|
7032
|
-
skipLexicalRootCheck: params.resolveParams.skipLexicalRootCheck,
|
|
7033
|
-
lexicalInside,
|
|
7034
|
-
canonicalOutsideLexicalPath,
|
|
7035
|
-
rootCanonicalPath: params.rootCanonicalPath,
|
|
7036
|
-
boundaryLabel: params.resolveParams.boundaryLabel,
|
|
7037
|
-
rootPath: params.rootPath,
|
|
7038
|
-
absolutePath: params.absolutePath,
|
|
7039
|
-
});
|
|
7040
|
-
return {
|
|
7041
|
-
rootPath: params.rootPath,
|
|
7042
|
-
absolutePath: params.absolutePath,
|
|
7043
|
-
rootCanonicalPath: params.rootCanonicalPath,
|
|
7044
|
-
lexicalInside,
|
|
7045
|
-
canonicalOutsideLexicalPath,
|
|
7046
|
-
};
|
|
7047
|
-
}
|
|
7048
|
-
async function resolveOutsideRootPathAsync(params) {
|
|
7049
|
-
if (params.context.lexicalInside) {
|
|
7050
|
-
return null;
|
|
7189
|
+
async function drain() {
|
|
7190
|
+
for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
|
|
7191
|
+
await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
|
|
7192
|
+
}
|
|
7051
7193
|
}
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
boundaryLabel: params.boundaryLabel,
|
|
7055
|
-
context: params.context,
|
|
7056
|
-
kind,
|
|
7057
|
-
});
|
|
7058
|
-
}
|
|
7059
|
-
function resolveOutsideRootPathSync(params) {
|
|
7060
|
-
if (params.context.lexicalInside) {
|
|
7061
|
-
return null;
|
|
7194
|
+
function reset() {
|
|
7195
|
+
releaseAllLocksSync(state);
|
|
7062
7196
|
}
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
}
|
|
7070
|
-
|
|
7071
|
-
return buildOutsideLexicalRootPath({
|
|
7072
|
-
boundaryLabel: params.boundaryLabel,
|
|
7073
|
-
rootCanonicalPath: params.context.rootCanonicalPath,
|
|
7074
|
-
absolutePath: params.context.absolutePath,
|
|
7075
|
-
canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
|
|
7076
|
-
rootPath: params.context.rootPath,
|
|
7077
|
-
kind: params.kind,
|
|
7078
|
-
});
|
|
7079
|
-
}
|
|
7080
|
-
async function resolveOutsideLexicalCanonicalPathAsync(params) {
|
|
7081
|
-
if (path_isPathInside(params.rootPath, params.absolutePath)) {
|
|
7082
|
-
return undefined;
|
|
7197
|
+
function heldEntries() {
|
|
7198
|
+
return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
|
|
7199
|
+
normalizedTargetPath,
|
|
7200
|
+
lockPath: held.lockPath,
|
|
7201
|
+
acquiredAt: held.acquiredAt,
|
|
7202
|
+
metadata: held.metadata,
|
|
7203
|
+
forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
|
|
7204
|
+
}));
|
|
7083
7205
|
}
|
|
7084
|
-
return
|
|
7206
|
+
return { acquire, withLock, drain, reset, heldEntries };
|
|
7085
7207
|
}
|
|
7086
|
-
function
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
}
|
|
7090
|
-
return resolvePathViaExistingAncestorSync(params.absolutePath);
|
|
7208
|
+
async function withSidecarLock(targetPath, options, fn) {
|
|
7209
|
+
const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
|
|
7210
|
+
const { managerKey: _managerKey, ...acquireOptions } = options;
|
|
7211
|
+
return await manager.withLock({ ...acquireOptions, targetPath }, fn);
|
|
7091
7212
|
}
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7213
|
+
|
|
7214
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
|
|
7215
|
+
|
|
7216
|
+
|
|
7217
|
+
|
|
7218
|
+
|
|
7219
|
+
|
|
7220
|
+
|
|
7221
|
+
|
|
7222
|
+
|
|
7223
|
+
|
|
7224
|
+
|
|
7225
|
+
|
|
7226
|
+
|
|
7227
|
+
|
|
7228
|
+
|
|
7229
|
+
|
|
7230
|
+
|
|
7231
|
+
|
|
7232
|
+
function pinned_write_byteLength(input, encoding) {
|
|
7233
|
+
return typeof input === "string"
|
|
7234
|
+
? Buffer.byteLength(input, encoding ?? "utf8")
|
|
7235
|
+
: input.byteLength;
|
|
7106
7236
|
}
|
|
7107
|
-
function
|
|
7108
|
-
if (
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7237
|
+
function assertSafeBasename(basename) {
|
|
7238
|
+
if (!basename ||
|
|
7239
|
+
basename === "." ||
|
|
7240
|
+
basename === ".." ||
|
|
7241
|
+
basename.includes("/") ||
|
|
7242
|
+
basename.includes("\0")) {
|
|
7243
|
+
throw new errors_FsSafeError("invalid-path", "invalid target path");
|
|
7113
7244
|
}
|
|
7114
|
-
throw pathEscapeError({
|
|
7115
|
-
boundaryLabel: params.boundaryLabel,
|
|
7116
|
-
rootPath: params.rootPath,
|
|
7117
|
-
absolutePath: params.absolutePath,
|
|
7118
|
-
});
|
|
7119
7245
|
}
|
|
7120
|
-
function
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
rootPath: params.rootPath,
|
|
7125
|
-
rootCanonicalPath: params.rootCanonicalPath,
|
|
7126
|
-
relativePath: relativeInsideRoot(params.rootCanonicalPath, params.canonicalPath),
|
|
7127
|
-
exists: params.kind.exists,
|
|
7128
|
-
kind: params.kind.kind,
|
|
7129
|
-
};
|
|
7246
|
+
function assertWithinMaxBytes(bytes, maxBytes) {
|
|
7247
|
+
if (maxBytes !== undefined && bytes > maxBytes) {
|
|
7248
|
+
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
7249
|
+
}
|
|
7130
7250
|
}
|
|
7131
|
-
async function
|
|
7251
|
+
async function syncFileBestEffort(handle) {
|
|
7132
7252
|
try {
|
|
7133
|
-
|
|
7134
|
-
? await promises_.lstat(absolutePath)
|
|
7135
|
-
: await promises_.stat(absolutePath);
|
|
7136
|
-
return { exists: true, kind: toResolvedKind(stat) };
|
|
7253
|
+
await handle.sync();
|
|
7137
7254
|
}
|
|
7138
7255
|
catch (error) {
|
|
7139
|
-
if (
|
|
7140
|
-
|
|
7256
|
+
if (error?.code !== "EPERM") {
|
|
7257
|
+
throw error;
|
|
7141
7258
|
}
|
|
7142
|
-
throw error;
|
|
7143
7259
|
}
|
|
7144
7260
|
}
|
|
7145
|
-
function
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7261
|
+
async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
7262
|
+
let bytes = 0;
|
|
7263
|
+
for await (const chunk of stream) {
|
|
7264
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
7265
|
+
bytes += buffer.byteLength;
|
|
7266
|
+
assertWithinMaxBytes(bytes, maxBytes);
|
|
7267
|
+
let offset = 0;
|
|
7268
|
+
while (offset < buffer.byteLength) {
|
|
7269
|
+
const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
|
|
7270
|
+
if (bytesWritten <= 0) {
|
|
7271
|
+
throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
|
|
7272
|
+
}
|
|
7273
|
+
offset += bytesWritten;
|
|
7153
7274
|
}
|
|
7154
|
-
throw error;
|
|
7155
7275
|
}
|
|
7156
7276
|
}
|
|
7157
|
-
function
|
|
7158
|
-
|
|
7159
|
-
|
|
7277
|
+
async function runPinnedWriteHelper(params) {
|
|
7278
|
+
const normalizedParams = { ...params, maxBytes: byte_budget_normalizeMaxBytes(params.maxBytes) };
|
|
7279
|
+
assertSafeBasename(params.basename);
|
|
7280
|
+
validatePinnedOperationPayload({
|
|
7281
|
+
relativeParentPath: params.relativeParentPath,
|
|
7282
|
+
});
|
|
7283
|
+
// The explicit compatibility policy uses the guarded Node fallback, where
|
|
7284
|
+
// content verification can replace the strict post-rename inode check.
|
|
7285
|
+
if (normalizedParams.onRenameIdentityMismatch === "verify-content") {
|
|
7286
|
+
return await runPinnedWriteFallback(normalizedParams);
|
|
7160
7287
|
}
|
|
7161
|
-
|
|
7162
|
-
|
|
7288
|
+
const native = getNativeBinding();
|
|
7289
|
+
if (native) {
|
|
7290
|
+
return await runPinnedWriteNative(native, normalizedParams);
|
|
7163
7291
|
}
|
|
7164
|
-
|
|
7165
|
-
|
|
7292
|
+
return await runPinnedWriteFallback(normalizedParams);
|
|
7293
|
+
}
|
|
7294
|
+
async function runPinnedWriteWithRenamePolicy(params) {
|
|
7295
|
+
const { targetPath, renameIdentity, ...writeParams } = params;
|
|
7296
|
+
if (renameIdentity !== "verify-content-with-lock") {
|
|
7297
|
+
return await runPinnedWriteHelper(writeParams);
|
|
7166
7298
|
}
|
|
7167
|
-
|
|
7299
|
+
const relativeTargetPath = writeParams.relativeParentPath
|
|
7300
|
+
? `${writeParams.relativeParentPath}/${writeParams.basename}`
|
|
7301
|
+
: writeParams.basename;
|
|
7302
|
+
const lockPath = external_node_path_.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
|
|
7303
|
+
return await withSidecarLock(writeParams.rootPath, {
|
|
7304
|
+
managerKey: `fs-safe.write:${targetPath}`,
|
|
7305
|
+
lockPath,
|
|
7306
|
+
staleMs: 30_000,
|
|
7307
|
+
timeoutMs: 5_000,
|
|
7308
|
+
payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
|
|
7309
|
+
retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
|
|
7310
|
+
}, async () => await runPinnedWriteHelper({
|
|
7311
|
+
...writeParams,
|
|
7312
|
+
onRenameIdentityMismatch: "verify-content",
|
|
7313
|
+
}));
|
|
7168
7314
|
}
|
|
7169
|
-
function
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7315
|
+
async function runPinnedWriteFallback(params) {
|
|
7316
|
+
let parentPath = params.relativeParentPath
|
|
7317
|
+
? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
7318
|
+
: params.rootPath;
|
|
7319
|
+
if (params.mkdir) {
|
|
7320
|
+
// mkdirPathComponentsWithGuards may resolve the final component through
|
|
7321
|
+
// an in-root symlink (e.g. a skill-bank layout). Use its returned real
|
|
7322
|
+
// path for the subsequent guard and target path so we don't re-check the
|
|
7323
|
+
// original, possibly-symlinked, lexical path and reject it outright.
|
|
7324
|
+
parentPath = await mkdirPathComponentsWithGuards({
|
|
7325
|
+
rootReal: params.rootPath,
|
|
7326
|
+
targetPath: parentPath,
|
|
7327
|
+
beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
|
|
7328
|
+
});
|
|
7173
7329
|
}
|
|
7174
|
-
|
|
7175
|
-
|
|
7330
|
+
const parentGuard = params.mkdir
|
|
7331
|
+
? await directory_guard_createAsyncDirectoryGuard(parentPath)
|
|
7332
|
+
: await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
7333
|
+
const targetPath = external_node_path_.join(parentPath, params.basename);
|
|
7334
|
+
if (params.overwrite === false) {
|
|
7335
|
+
const handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_.open(targetPath, external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL, params.mode), {
|
|
7336
|
+
onPostGuardFailure: async (openedHandle) => {
|
|
7337
|
+
// The parent failed verification, so targetPath may now resolve
|
|
7338
|
+
// somewhere else. Close the fd, but do not clean up by path.
|
|
7339
|
+
await openedHandle.close().catch(() => undefined);
|
|
7340
|
+
},
|
|
7341
|
+
});
|
|
7342
|
+
let created = true;
|
|
7343
|
+
try {
|
|
7344
|
+
const verificationIdentity = await handle.stat({ bigint: true });
|
|
7345
|
+
await handle.chmod(params.mode);
|
|
7346
|
+
if (params.input.kind === "buffer") {
|
|
7347
|
+
assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
|
|
7348
|
+
if (typeof params.input.data === "string") {
|
|
7349
|
+
await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
|
|
7350
|
+
}
|
|
7351
|
+
else {
|
|
7352
|
+
await handle.writeFile(params.input.data);
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
else {
|
|
7356
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
7357
|
+
}
|
|
7358
|
+
await syncFileBestEffort(handle);
|
|
7359
|
+
const stat = await handle.stat();
|
|
7360
|
+
await syncDirectoryBestEffort(parentPath);
|
|
7361
|
+
// Publication is complete. A failed outer check must not remove its target.
|
|
7362
|
+
created = false;
|
|
7363
|
+
await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
|
|
7364
|
+
return { dev: stat.dev, ino: stat.ino };
|
|
7365
|
+
}
|
|
7366
|
+
finally {
|
|
7367
|
+
await handle.close().catch(() => undefined);
|
|
7368
|
+
if (created) {
|
|
7369
|
+
await promises_.rm(targetPath, { force: true }).catch(() => undefined);
|
|
7370
|
+
}
|
|
7371
|
+
}
|
|
7176
7372
|
}
|
|
7177
|
-
|
|
7178
|
-
}
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7373
|
+
// Private staging must not consume the destination basename's filename budget.
|
|
7374
|
+
const tempPath = external_node_path_.join(parentPath, `.fs-safe-${(0,external_node_crypto_.randomUUID)()}.tmp`);
|
|
7375
|
+
const tempFlags = external_node_fs_.constants.O_WRONLY |
|
|
7376
|
+
external_node_fs_.constants.O_CREAT |
|
|
7377
|
+
external_node_fs_.constants.O_EXCL |
|
|
7378
|
+
(process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_.constants
|
|
7379
|
+
? external_node_fs_.constants.O_NOFOLLOW
|
|
7380
|
+
: 0);
|
|
7381
|
+
let handle;
|
|
7382
|
+
let tempStat;
|
|
7383
|
+
let readHandle;
|
|
7384
|
+
let renamed = false;
|
|
7385
|
+
try {
|
|
7386
|
+
handle = await promises_.open(tempPath, tempFlags, params.mode);
|
|
7387
|
+
let verificationIdentity = await handle.stat({ bigint: true });
|
|
7388
|
+
await handle.chmod(params.mode);
|
|
7389
|
+
if (params.input.kind === "buffer") {
|
|
7390
|
+
assertWithinMaxBytes(pinned_write_byteLength(params.input.data, params.input.encoding), params.maxBytes);
|
|
7391
|
+
if (typeof params.input.data === "string") {
|
|
7392
|
+
await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
|
|
7393
|
+
}
|
|
7394
|
+
else {
|
|
7395
|
+
await handle.writeFile(params.input.data);
|
|
7396
|
+
}
|
|
7397
|
+
}
|
|
7398
|
+
else {
|
|
7399
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
7400
|
+
}
|
|
7401
|
+
tempStat = await handle.stat();
|
|
7402
|
+
const tempPathStat = await promises_.lstat(tempPath);
|
|
7403
|
+
if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
|
|
7404
|
+
throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
|
|
7405
|
+
}
|
|
7406
|
+
const expectedTempStat = tempStat;
|
|
7407
|
+
await syncFileBestEffort(handle);
|
|
7408
|
+
let verifiedIdentity = expectedTempStat;
|
|
7409
|
+
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
7410
|
+
await promises_.rename(tempPath, targetPath);
|
|
7411
|
+
renamed = true;
|
|
7412
|
+
await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
|
|
7413
|
+
await syncDirectoryBestEffort(parentPath);
|
|
7414
|
+
const targetStat = await promises_.lstat(targetPath);
|
|
7415
|
+
if (targetStat.isSymbolicLink()) {
|
|
7416
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
7417
|
+
}
|
|
7418
|
+
if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
|
|
7419
|
+
// On filesystems like rclone FUSE, rename(2) can give the destination a
|
|
7420
|
+
// different inode from the source temp fd even with zero concurrency. The
|
|
7421
|
+
// caller must ensure mutual exclusion before passing "verify-content";
|
|
7422
|
+
// fall back to a content hash for this rename-boundary check only.
|
|
7423
|
+
if (params.onRenameIdentityMismatch !== "verify-content") {
|
|
7424
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
7425
|
+
}
|
|
7426
|
+
if (params.input.kind !== "buffer") {
|
|
7427
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
7428
|
+
}
|
|
7429
|
+
const expectedHash = sha256Hex(params.input.data, params.input.encoding);
|
|
7430
|
+
readHandle = await promises_.open(targetPath, read_open_flags_resolveReadOpenFlags());
|
|
7431
|
+
const readHandleStat = await readHandle.stat({ bigint: true });
|
|
7432
|
+
const actualHash = sha256Hex(await readHandle.readFile());
|
|
7433
|
+
if (actualHash !== expectedHash) {
|
|
7434
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
7435
|
+
}
|
|
7436
|
+
// The content-verified destination, not the old temp inode, is now pinned.
|
|
7437
|
+
verificationIdentity = readHandleStat;
|
|
7438
|
+
// Preserve the helper's legacy numeric return facts, not the private proof.
|
|
7439
|
+
verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
|
|
7440
|
+
}
|
|
7441
|
+
});
|
|
7442
|
+
await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
|
|
7443
|
+
return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
|
|
7444
|
+
}
|
|
7445
|
+
finally {
|
|
7446
|
+
await readHandle?.close().catch(() => undefined);
|
|
7447
|
+
await handle?.close().catch(() => undefined);
|
|
7448
|
+
if (!renamed) {
|
|
7449
|
+
await promises_.rm(tempPath, { force: true }).catch(() => undefined);
|
|
7450
|
+
}
|
|
7182
7451
|
}
|
|
7183
|
-
throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
|
|
7184
|
-
}
|
|
7185
|
-
function pathEscapeError(params) {
|
|
7186
|
-
return new Error(`Path escapes ${params.boundaryLabel} (${shortPath(params.rootPath)}): ${shortPath(params.absolutePath)}`);
|
|
7187
|
-
}
|
|
7188
|
-
function symlinkEscapeError(params) {
|
|
7189
|
-
return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
|
|
7190
7452
|
}
|
|
7191
7453
|
|
|
7192
7454
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path-policy.js
|
|
@@ -8135,56 +8397,6 @@ async function verifyAtomicWriteResult(params) {
|
|
|
8135
8397
|
}
|
|
8136
8398
|
}
|
|
8137
8399
|
|
|
8138
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/strict-file-identity.js
|
|
8139
|
-
|
|
8140
|
-
function identityMismatch() {
|
|
8141
|
-
return new errors_FsSafeError("path-mismatch", "file identity changed or could not be verified");
|
|
8142
|
-
}
|
|
8143
|
-
function identityCheck(expected, platform) {
|
|
8144
|
-
const known = {};
|
|
8145
|
-
const check = (stat) => {
|
|
8146
|
-
let complete = true;
|
|
8147
|
-
for (const field of ["dev", "ino"]) {
|
|
8148
|
-
const value = stat[field];
|
|
8149
|
-
// Numeric receipts cannot recover identity bits already lost to rounding.
|
|
8150
|
-
if (typeof value !== "bigint")
|
|
8151
|
-
throw identityMismatch();
|
|
8152
|
-
if (platform === "win32" && value === 0n) {
|
|
8153
|
-
complete = false;
|
|
8154
|
-
}
|
|
8155
|
-
else {
|
|
8156
|
-
if (known[field] !== undefined && known[field] !== value)
|
|
8157
|
-
throw identityMismatch();
|
|
8158
|
-
known[field] = value;
|
|
8159
|
-
}
|
|
8160
|
-
}
|
|
8161
|
-
return complete;
|
|
8162
|
-
};
|
|
8163
|
-
if (expected && !check(expected))
|
|
8164
|
-
throw identityMismatch();
|
|
8165
|
-
return check;
|
|
8166
|
-
}
|
|
8167
|
-
// Retry only unknown Windows identities, retaining every known component so a
|
|
8168
|
-
// later observation cannot erase a definite mismatch. Never reopen the file.
|
|
8169
|
-
async function inspectFileIdentity(inspect, expected, platform = process.platform) {
|
|
8170
|
-
const check = identityCheck(expected, platform);
|
|
8171
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
8172
|
-
const stat = await inspect();
|
|
8173
|
-
if (check(stat))
|
|
8174
|
-
return stat;
|
|
8175
|
-
}
|
|
8176
|
-
throw identityMismatch();
|
|
8177
|
-
}
|
|
8178
|
-
function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
|
|
8179
|
-
const check = identityCheck(expected, platform);
|
|
8180
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
8181
|
-
const stat = inspect();
|
|
8182
|
-
if (check(stat))
|
|
8183
|
-
return stat;
|
|
8184
|
-
}
|
|
8185
|
-
throw identityMismatch();
|
|
8186
|
-
}
|
|
8187
|
-
|
|
8188
8400
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-impl.js
|
|
8189
8401
|
|
|
8190
8402
|
|
|
@@ -8215,6 +8427,7 @@ function inspectFileIdentitySync(inspect, expected, platform = process.platform)
|
|
|
8215
8427
|
|
|
8216
8428
|
|
|
8217
8429
|
|
|
8430
|
+
|
|
8218
8431
|
|
|
8219
8432
|
|
|
8220
8433
|
function logWarn(message) {
|
|
@@ -8290,25 +8503,15 @@ async function openVerifiedLocalFile(filePath, options) {
|
|
|
8290
8503
|
if (preOpenStat) {
|
|
8291
8504
|
await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
|
|
8292
8505
|
}
|
|
8506
|
+
const openFlags = options?.symlinks === "follow-within-root"
|
|
8507
|
+
? OPEN_READ_FOLLOW_FLAGS
|
|
8508
|
+
: OPEN_READ_FLAGS;
|
|
8509
|
+
await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
|
|
8293
8510
|
let handle;
|
|
8294
8511
|
try {
|
|
8295
|
-
|
|
8296
|
-
? OPEN_READ_FOLLOW_FLAGS
|
|
8297
|
-
: OPEN_READ_FLAGS;
|
|
8298
|
-
await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
|
|
8299
|
-
handle = await promises_.open(filePath, openFlags);
|
|
8300
|
-
try {
|
|
8301
|
-
await fsSafeTestHooks?.afterOpen?.(filePath, handle);
|
|
8302
|
-
}
|
|
8303
|
-
catch (err) {
|
|
8304
|
-
await handle.close().catch(() => { });
|
|
8305
|
-
throw err;
|
|
8306
|
-
}
|
|
8512
|
+
handle = await promises_.open(filePath, openFlags).catch((error) => recordFileOpenFailure(path_isNotFoundPathError(error) ? fileNotFoundError() : error, filePath));
|
|
8307
8513
|
}
|
|
8308
8514
|
catch (err) {
|
|
8309
|
-
if (path_isNotFoundPathError(err)) {
|
|
8310
|
-
throw fileNotFoundError();
|
|
8311
|
-
}
|
|
8312
8515
|
if (isSymlinkOpenError(err)) {
|
|
8313
8516
|
throw new errors_FsSafeError("symlink", "symlink open blocked", { cause: err });
|
|
8314
8517
|
}
|
|
@@ -8319,16 +8522,35 @@ async function openVerifiedLocalFile(filePath, options) {
|
|
|
8319
8522
|
throw err;
|
|
8320
8523
|
}
|
|
8321
8524
|
try {
|
|
8525
|
+
await fsSafeTestHooks?.afterOpen?.(filePath, handle);
|
|
8322
8526
|
const stat = await handle.stat();
|
|
8323
8527
|
if (!stat.isFile()) {
|
|
8324
8528
|
throw new errors_FsSafeError("not-file", "not a file");
|
|
8325
8529
|
}
|
|
8326
8530
|
// Keep numeric Stats for the public receipt, never for identity verification.
|
|
8327
|
-
|
|
8531
|
+
let openedIdentity;
|
|
8532
|
+
const identity = await inspectFileIdentity(async () => (openedIdentity = await handle.stat({ bigint: true })), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined).catch(async (error) => {
|
|
8533
|
+
if ([0, 1].includes(stat.nlink)) {
|
|
8534
|
+
await recordPreOpenFileChange(error, handle, filePath, preOpenStat, openedIdentity);
|
|
8535
|
+
}
|
|
8536
|
+
throw error;
|
|
8537
|
+
});
|
|
8328
8538
|
if (options?.hardlinks === "reject" && stat.nlink > 1) {
|
|
8329
8539
|
throw hardlinkedPathNotAllowedError();
|
|
8330
8540
|
}
|
|
8331
|
-
|
|
8541
|
+
const inspectPathIdentity = async (inspect) => {
|
|
8542
|
+
try {
|
|
8543
|
+
return await inspectFileIdentity(inspect, identity);
|
|
8544
|
+
}
|
|
8545
|
+
catch (error) {
|
|
8546
|
+
const failure = path_isNotFoundPathError(error) ? openedPathResolutionError(fileNotFoundError()) : error;
|
|
8547
|
+
if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
|
|
8548
|
+
await recordOpenedFileFailure(failure, handle, filePath, identity);
|
|
8549
|
+
}
|
|
8550
|
+
throw failure;
|
|
8551
|
+
}
|
|
8552
|
+
};
|
|
8553
|
+
await inspectPathIdentity(async () => {
|
|
8332
8554
|
const pathStat = options?.symlinks === "follow-within-root"
|
|
8333
8555
|
? await promises_.stat(filePath, { bigint: true })
|
|
8334
8556
|
: await promises_.lstat(filePath, { bigint: true });
|
|
@@ -8336,26 +8558,26 @@ async function openVerifiedLocalFile(filePath, options) {
|
|
|
8336
8558
|
throw new errors_FsSafeError("symlink", "symlink not allowed");
|
|
8337
8559
|
}
|
|
8338
8560
|
return pathStat;
|
|
8339
|
-
}
|
|
8561
|
+
});
|
|
8340
8562
|
await fsSafeTestHooks?.afterOpenedPathIdentityCheck?.(filePath, handle);
|
|
8341
|
-
const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath)
|
|
8342
|
-
|
|
8563
|
+
const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath)
|
|
8564
|
+
.catch(async (error) => {
|
|
8565
|
+
if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
|
|
8566
|
+
await recordOpenedFileFailure(error, handle, filePath, identity);
|
|
8567
|
+
}
|
|
8568
|
+
throw error;
|
|
8569
|
+
});
|
|
8570
|
+
await inspectPathIdentity(async () => {
|
|
8343
8571
|
const realStat = await promises_.stat(realPath, { bigint: true });
|
|
8344
8572
|
if (options?.hardlinks === "reject" && realStat.nlink > 1n) {
|
|
8345
8573
|
throw hardlinkedPathNotAllowedError();
|
|
8346
8574
|
}
|
|
8347
8575
|
return realStat;
|
|
8348
|
-
}
|
|
8576
|
+
});
|
|
8349
8577
|
return { opened: openResult({ handle, realPath, stat }), identity };
|
|
8350
8578
|
}
|
|
8351
8579
|
catch (err) {
|
|
8352
8580
|
await handle.close().catch(() => { });
|
|
8353
|
-
if (err instanceof errors_FsSafeError) {
|
|
8354
|
-
throw err;
|
|
8355
|
-
}
|
|
8356
|
-
if (path_isNotFoundPathError(err)) {
|
|
8357
|
-
throw fileNotFoundError();
|
|
8358
|
-
}
|
|
8359
8581
|
throw err;
|
|
8360
8582
|
}
|
|
8361
8583
|
}
|
|
@@ -8584,19 +8806,10 @@ async function openFileInRoot(root, params) {
|
|
|
8584
8806
|
rejectUnsafeDeviceReads: true,
|
|
8585
8807
|
rejectSymlinks: params.symlinks !== "follow-within-root",
|
|
8586
8808
|
});
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
symlinks: params.symlinks,
|
|
8592
|
-
}));
|
|
8593
|
-
}
|
|
8594
|
-
catch (err) {
|
|
8595
|
-
if (err instanceof errors_FsSafeError) {
|
|
8596
|
-
throw err;
|
|
8597
|
-
}
|
|
8598
|
-
throw err;
|
|
8599
|
-
}
|
|
8809
|
+
const { opened } = await openVerifiedLocalFile(resolved, {
|
|
8810
|
+
nonBlockingRead: params.nonBlockingRead,
|
|
8811
|
+
symlinks: params.symlinks,
|
|
8812
|
+
});
|
|
8600
8813
|
if (params.hardlinks !== "allow" && opened.stat.nlink > 1) {
|
|
8601
8814
|
await opened.handle.close().catch(() => { });
|
|
8602
8815
|
throw hardlinkedPathNotAllowedError();
|
|
@@ -8648,9 +8861,7 @@ function emitWriteBoundaryWarning(reason) {
|
|
|
8648
8861
|
logWarn(`security: fs-safe write boundary warning (${reason})`);
|
|
8649
8862
|
}
|
|
8650
8863
|
function buildAtomicWriteTempPath(targetPath) {
|
|
8651
|
-
|
|
8652
|
-
const base = external_node_path_.basename(targetPath);
|
|
8653
|
-
return external_node_path_.join(dir, `.${base}.${process.pid}.${(0,external_node_crypto_.randomUUID)()}.tmp`);
|
|
8864
|
+
return external_node_path_.join(external_node_path_.dirname(targetPath), `.fs-safe-${(0,external_node_crypto_.randomUUID)()}.tmp`);
|
|
8654
8865
|
}
|
|
8655
8866
|
function rootWriteQueueKey(root, relativePath) {
|
|
8656
8867
|
return `${root.rootReal}\0${relativePath}`;
|
|
@@ -8889,7 +9100,7 @@ async function writeFileInRoot(root, params) {
|
|
|
8889
9100
|
await writeFileFallback(root, params);
|
|
8890
9101
|
return;
|
|
8891
9102
|
}
|
|
8892
|
-
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
|
|
9103
|
+
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations, params.overwrite);
|
|
8893
9104
|
await serializePathWrite(pinned.targetPath, async () => {
|
|
8894
9105
|
await commitPinnedWriteInRoot(root, pinned, params);
|
|
8895
9106
|
});
|
|
@@ -9011,7 +9222,7 @@ async function removePathIfIdentityUnchanged(targetPath, identity) {
|
|
|
9011
9222
|
await promises_.rm(targetPath);
|
|
9012
9223
|
});
|
|
9013
9224
|
}
|
|
9014
|
-
async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
|
|
9225
|
+
async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations, overwrite = true) {
|
|
9015
9226
|
const { rootReal, rootWithSep, resolved } = await resolveGuardedWritePathInRoot(root, {
|
|
9016
9227
|
relativePath,
|
|
9017
9228
|
denyMutations,
|
|
@@ -9029,22 +9240,40 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode,
|
|
|
9029
9240
|
if (!basename || basename === "." || basename === "/") {
|
|
9030
9241
|
throw new errors_FsSafeError("invalid-path", "invalid target path");
|
|
9031
9242
|
}
|
|
9243
|
+
// Exclusive publication cannot inherit an existing file's mode. Keep type
|
|
9244
|
+
// and alias checks, but never open a competing owner's record just to reject it.
|
|
9245
|
+
if (!overwrite) {
|
|
9246
|
+
try {
|
|
9247
|
+
const existing = await promises_.stat(resolved);
|
|
9248
|
+
if (!existing.isFile())
|
|
9249
|
+
throw new errors_FsSafeError("not-file", "not a file");
|
|
9250
|
+
if (existing.nlink > 1)
|
|
9251
|
+
throw hardlinkedPathNotAllowedError();
|
|
9252
|
+
throw new errors_FsSafeError("already-exists", "file already exists");
|
|
9253
|
+
}
|
|
9254
|
+
catch (error) {
|
|
9255
|
+
if (!path_isNotFoundPathError(error))
|
|
9256
|
+
throw error;
|
|
9257
|
+
}
|
|
9258
|
+
}
|
|
9032
9259
|
let mode = requestedMode ?? 0o600;
|
|
9033
9260
|
try {
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9261
|
+
if (overwrite) {
|
|
9262
|
+
const opened = await openFileInRoot(root, {
|
|
9263
|
+
relativePath,
|
|
9264
|
+
hardlinks: "reject",
|
|
9265
|
+
nonBlockingRead: true,
|
|
9266
|
+
symlinks: "follow-within-root",
|
|
9267
|
+
});
|
|
9268
|
+
try {
|
|
9269
|
+
mode = requestedMode ?? (opened.stat.mode & 0o777);
|
|
9270
|
+
if (!path_isPathInside(rootWithSep, opened.realPath)) {
|
|
9271
|
+
throw outsideWorkspaceError();
|
|
9272
|
+
}
|
|
9273
|
+
}
|
|
9274
|
+
finally {
|
|
9275
|
+
await opened.handle.close().catch(() => { });
|
|
9044
9276
|
}
|
|
9045
|
-
}
|
|
9046
|
-
finally {
|
|
9047
|
-
await opened.handle.close().catch(() => { });
|
|
9048
9277
|
}
|
|
9049
9278
|
}
|
|
9050
9279
|
catch (err) {
|
|
@@ -94039,4 +94268,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
|
|
|
94039
94268
|
// module factories are used so entry inlining is disabled
|
|
94040
94269
|
// startup
|
|
94041
94270
|
// Load entry module and return exports
|
|
94042
|
-
var __webpack_exports__ = __webpack_require__(
|
|
94271
|
+
var __webpack_exports__ = __webpack_require__(8853);
|