@lousy-agents/agent-shell 5.18.0 → 5.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1499 -1363
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __rspack_createRequire } from "node:module";
|
|
3
3
|
const __rspack_createRequire_require = __rspack_createRequire(import.meta.url);
|
|
4
4
|
var __webpack_modules__ = ({
|
|
5
|
-
|
|
5
|
+
418(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
|
|
6
6
|
|
|
7
7
|
;// CONCATENATED MODULE: external "node:child_process"
|
|
8
8
|
const external_node_child_process_namespaceObject = __rspack_createRequire_require("node:child_process");
|
|
@@ -91,42 +91,18 @@ function categorizeFsSafeError(code) {
|
|
|
91
91
|
class errors_FsSafeError extends Error {
|
|
92
92
|
code;
|
|
93
93
|
category;
|
|
94
|
+
details;
|
|
94
95
|
constructor(code, message, options = {}) {
|
|
95
96
|
super(message, options);
|
|
96
97
|
this.name = "FsSafeError";
|
|
97
98
|
this.code = code;
|
|
98
99
|
this.category = categorizeFsSafeError(code);
|
|
100
|
+
this.details = options.details;
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
;// CONCATENATED MODULE: external "node:fs"
|
|
103
105
|
const external_node_fs_namespaceObject = __rspack_createRequire_require("node:fs");
|
|
104
|
-
;// CONCATENATED MODULE: external "node:stream/promises"
|
|
105
|
-
const external_node_stream_promises_namespaceObject = __rspack_createRequire_require("node:stream/promises");
|
|
106
|
-
;// CONCATENATED MODULE: external "node:stream"
|
|
107
|
-
const external_node_stream_namespaceObject = __rspack_createRequire_require("node:stream");
|
|
108
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read-stream.js
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
function createMaxBytesTransform(maxBytes) {
|
|
112
|
-
let bytes = 0;
|
|
113
|
-
return new external_node_stream_namespaceObject.Transform({
|
|
114
|
-
transform(chunk, _encoding, callback) {
|
|
115
|
-
const buffer = chunk instanceof Buffer ? chunk : Buffer.from(chunk);
|
|
116
|
-
bytes += buffer.byteLength;
|
|
117
|
-
if (bytes > maxBytes) {
|
|
118
|
-
callback(new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
callback(null, buffer);
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
function createBoundedReadStream(opened, maxBytes) {
|
|
126
|
-
const stream = opened.handle.createReadStream();
|
|
127
|
-
return maxBytes === undefined ? stream : stream.pipe(createMaxBytesTransform(maxBytes));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
106
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/file-identity.js
|
|
131
107
|
|
|
132
108
|
function isZero(value) {
|
|
@@ -151,86 +127,11 @@ function file_identity_sameFileIdentity(left, right, platform = process.platform
|
|
|
151
127
|
return platform === "win32" && (isZero(left.dev) || isZero(right.dev));
|
|
152
128
|
}
|
|
153
129
|
|
|
154
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/string-coerce.js
|
|
155
|
-
function readStringValue(value) {
|
|
156
|
-
return typeof value === "string" ? value : undefined;
|
|
157
|
-
}
|
|
158
|
-
function normalizeNullableString(value) {
|
|
159
|
-
if (typeof value !== "string") {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
const trimmed = value.trim();
|
|
163
|
-
return trimmed ? trimmed : null;
|
|
164
|
-
}
|
|
165
|
-
function normalizeOptionalString(value) {
|
|
166
|
-
return normalizeNullableString(value) ?? undefined;
|
|
167
|
-
}
|
|
168
|
-
function normalizeStringifiedOptionalString(value) {
|
|
169
|
-
if (typeof value === "string") {
|
|
170
|
-
return normalizeOptionalString(value);
|
|
171
|
-
}
|
|
172
|
-
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
173
|
-
return normalizeOptionalString(String(value));
|
|
174
|
-
}
|
|
175
|
-
return undefined;
|
|
176
|
-
}
|
|
177
|
-
function normalizeOptionalLowercaseString(value) {
|
|
178
|
-
return normalizeOptionalString(value)?.toLowerCase();
|
|
179
|
-
}
|
|
180
|
-
function normalizeLowercaseStringOrEmpty(value) {
|
|
181
|
-
return normalizeOptionalLowercaseString(value) ?? "";
|
|
182
|
-
}
|
|
183
|
-
function normalizeFastMode(raw) {
|
|
184
|
-
if (typeof raw === "boolean") {
|
|
185
|
-
return raw;
|
|
186
|
-
}
|
|
187
|
-
if (!raw) {
|
|
188
|
-
return undefined;
|
|
189
|
-
}
|
|
190
|
-
const key = normalizeLowercaseStringOrEmpty(raw);
|
|
191
|
-
if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
if (["on", "true", "yes", "1", "enable", "enabled", "fast"].includes(key)) {
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
|
-
return undefined;
|
|
198
|
-
}
|
|
199
|
-
function lowercasePreservingWhitespace(value) {
|
|
200
|
-
return value.toLowerCase();
|
|
201
|
-
}
|
|
202
|
-
function localeLowercasePreservingWhitespace(value) {
|
|
203
|
-
return value.toLocaleLowerCase();
|
|
204
|
-
}
|
|
205
|
-
function resolvePrimaryStringValue(value) {
|
|
206
|
-
if (typeof value === "string") {
|
|
207
|
-
return normalizeOptionalString(value);
|
|
208
|
-
}
|
|
209
|
-
if (!value || typeof value !== "object") {
|
|
210
|
-
return undefined;
|
|
211
|
-
}
|
|
212
|
-
return normalizeOptionalString(value.primary);
|
|
213
|
-
}
|
|
214
|
-
function normalizeOptionalThreadValue(value) {
|
|
215
|
-
if (typeof value === "number") {
|
|
216
|
-
return Number.isFinite(value) ? Math.trunc(value) : undefined;
|
|
217
|
-
}
|
|
218
|
-
return normalizeOptionalString(value);
|
|
219
|
-
}
|
|
220
|
-
function normalizeOptionalStringifiedId(value) {
|
|
221
|
-
const normalized = normalizeOptionalThreadValue(value);
|
|
222
|
-
return normalized == null ? undefined : String(normalized);
|
|
223
|
-
}
|
|
224
|
-
function hasNonEmptyString(value) {
|
|
225
|
-
return normalizeOptionalString(value) !== undefined;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
130
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path.js
|
|
229
131
|
|
|
230
132
|
|
|
231
133
|
|
|
232
134
|
|
|
233
|
-
|
|
234
135
|
const NOT_FOUND_CODES = new Set(["ENOENT", "ENOTDIR"]);
|
|
235
136
|
const SYMLINK_OPEN_CODES = new Set(["ELOOP", "EINVAL", "ENOTSUP"]);
|
|
236
137
|
const POSIX_SEPARATOR_CHAR_CODE = 0x2f;
|
|
@@ -242,7 +143,9 @@ function normalizeWindowsPathForComparison(input) {
|
|
|
242
143
|
normalized = `\\\\${normalized.slice(4)}`;
|
|
243
144
|
}
|
|
244
145
|
}
|
|
245
|
-
|
|
146
|
+
// Lowercase only: this value feeds Windows containment math, so surrounding
|
|
147
|
+
// whitespace is part of the path and must not be trimmed away.
|
|
148
|
+
return normalized.replaceAll("/", "\\").toLowerCase();
|
|
246
149
|
}
|
|
247
150
|
function isNodeError(value) {
|
|
248
151
|
return Boolean(value && typeof value === "object" && "code" in value);
|
|
@@ -741,6 +644,27 @@ function isSameOrChildPath(candidate, parent) {
|
|
|
741
644
|
const parentPrefix = parent.endsWith(external_node_path_namespaceObject.sep) ? parent : `${parent}${external_node_path_namespaceObject.sep}`;
|
|
742
645
|
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
743
646
|
}
|
|
647
|
+
async function realpathOrThrowNotFile(target) {
|
|
648
|
+
try {
|
|
649
|
+
return external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(target));
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
if (path_isNotFoundPathError(error)) {
|
|
653
|
+
// A dangling symlink (or a component removed between lstat and
|
|
654
|
+
// realpath) is not a usable directory component.
|
|
655
|
+
throw new errors_FsSafeError("not-file", "directory component must be a directory", {
|
|
656
|
+
cause: error instanceof Error ? error : undefined,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
throw error;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Creates each missing path component from `rootReal` down to `targetPath`,
|
|
664
|
+
* guarding every step. Returns the real (symlink-resolved) path of the final
|
|
665
|
+
* component so callers can guard/use that path directly instead of
|
|
666
|
+
* re-deriving it from the original, possibly-symlinked, lexical path.
|
|
667
|
+
*/
|
|
744
668
|
async function mkdirPathComponentsWithGuards(params) {
|
|
745
669
|
const root = external_node_path_namespaceObject.resolve(params.rootReal);
|
|
746
670
|
const rootCanonical = external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(root));
|
|
@@ -764,18 +688,39 @@ async function mkdirPathComponentsWithGuards(params) {
|
|
|
764
688
|
}
|
|
765
689
|
}
|
|
766
690
|
const stat = await promises_namespaceObject.lstat(next);
|
|
767
|
-
if (stat.isSymbolicLink()
|
|
691
|
+
if (!stat.isSymbolicLink() && !stat.isDirectory()) {
|
|
768
692
|
throw new errors_FsSafeError("not-file", "directory component must be a directory");
|
|
769
693
|
}
|
|
770
694
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
771
695
|
// segment at a time and realpath-check each segment before descending.
|
|
772
|
-
|
|
696
|
+
const nextReal = await realpathOrThrowNotFile(next);
|
|
697
|
+
if (!isSameOrChildPath(nextReal, rootCanonical)) {
|
|
773
698
|
throw new errors_FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
774
699
|
}
|
|
700
|
+
if (stat.isSymbolicLink()) {
|
|
701
|
+
// An existing path component may legitimately be a symlink to a real
|
|
702
|
+
// directory inside the root (e.g. a skill-bank layout). We already
|
|
703
|
+
// verified above that it resolves inside the root, so treat the
|
|
704
|
+
// resolved real path as the directory for the rest of this walk
|
|
705
|
+
// instead of rejecting it outright. Guard checks from here on operate
|
|
706
|
+
// on the real (non-symlink) path, preserving TOCTOU protection for
|
|
707
|
+
// every subsequent segment. Callers that need the final directory
|
|
708
|
+
// (e.g. to guard it themselves after this function returns) must use
|
|
709
|
+
// the returned resolved path, not their own lexical parent path.
|
|
710
|
+
const targetStat = await promises_namespaceObject.stat(nextReal);
|
|
711
|
+
if (!targetStat.isDirectory()) {
|
|
712
|
+
throw new errors_FsSafeError("not-file", "directory component must be a directory");
|
|
713
|
+
}
|
|
714
|
+
await directory_guard_createAsyncDirectoryGuard(nextReal);
|
|
715
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
716
|
+
current = nextReal;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
775
719
|
await directory_guard_createAsyncDirectoryGuard(next);
|
|
776
720
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
777
721
|
current = next;
|
|
778
722
|
}
|
|
723
|
+
return current;
|
|
779
724
|
}
|
|
780
725
|
|
|
781
726
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/guarded-mutation.js
|
|
@@ -1041,8 +986,20 @@ async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
|
|
|
1041
986
|
return null;
|
|
1042
987
|
}
|
|
1043
988
|
|
|
1044
|
-
;// CONCATENATED MODULE:
|
|
989
|
+
;// CONCATENATED MODULE: external "node:module"
|
|
990
|
+
const external_node_module_namespaceObject = __rspack_createRequire_require("node:module");
|
|
991
|
+
;// CONCATENATED MODULE: external "node:url"
|
|
992
|
+
const external_node_url_namespaceObject = __rspack_createRequire_require("node:url");
|
|
993
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native-config.js
|
|
1045
994
|
let overrideConfig = {};
|
|
995
|
+
let legacyWarningEmitted = false;
|
|
996
|
+
const legacyModeEnvKeys = ["FS_SAFE_PYTHON_MODE", "OPENCLAW_FS_SAFE_PYTHON_MODE"];
|
|
997
|
+
const legacyPathEnvKeys = [
|
|
998
|
+
"FS_SAFE_PYTHON",
|
|
999
|
+
"OPENCLAW_FS_SAFE_PYTHON",
|
|
1000
|
+
"OPENCLAW_PINNED_PYTHON",
|
|
1001
|
+
"OPENCLAW_PINNED_WRITE_PYTHON",
|
|
1002
|
+
];
|
|
1046
1003
|
function parseMode(value) {
|
|
1047
1004
|
if (!value) {
|
|
1048
1005
|
return undefined;
|
|
@@ -1059,680 +1016,495 @@ function parseMode(value) {
|
|
|
1059
1016
|
}
|
|
1060
1017
|
return undefined;
|
|
1061
1018
|
}
|
|
1062
|
-
function
|
|
1019
|
+
function configureFsSafeNative(config) {
|
|
1063
1020
|
overrideConfig = { ...overrideConfig, ...config };
|
|
1064
1021
|
}
|
|
1065
|
-
function
|
|
1022
|
+
function warnLegacyPythonConfiguration(source, mappedMode) {
|
|
1023
|
+
if (legacyWarningEmitted) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
legacyWarningEmitted = true;
|
|
1027
|
+
process.emitWarning(`${source} is a deprecated 0.4 compatibility bridge; mapped to native mode ` +
|
|
1028
|
+
`"${mappedMode}". Use configureFsSafeNative({ mode: "${mappedMode}" }) or ` +
|
|
1029
|
+
`FS_SAFE_NATIVE_MODE=${mappedMode}. Python interpreter paths are no longer used.`, { code: "FS_SAFE_PYTHON_DEPRECATED", type: "DeprecationWarning" });
|
|
1030
|
+
}
|
|
1031
|
+
function readLegacyPythonMode() {
|
|
1032
|
+
const configuredKeys = [...legacyModeEnvKeys, ...legacyPathEnvKeys].filter((key) => process.env[key] !== undefined);
|
|
1033
|
+
if (configuredKeys.length === 0) {
|
|
1034
|
+
return undefined;
|
|
1035
|
+
}
|
|
1036
|
+
const rawMode = legacyModeEnvKeys.map((key) => process.env[key]).find((value) => value !== undefined);
|
|
1037
|
+
const mappedMode = parseMode(rawMode) ?? "auto";
|
|
1038
|
+
warnLegacyPythonConfiguration(`Legacy ${configuredKeys.join(", ")}`, mappedMode);
|
|
1039
|
+
return mappedMode;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* @deprecated Compatibility bridge for 0.4 upgrades. Use configureFsSafeNative.
|
|
1043
|
+
*/
|
|
1044
|
+
function configureFsSafePython(config) {
|
|
1045
|
+
const mappedMode = config.mode ?? "auto";
|
|
1046
|
+
warnLegacyPythonConfiguration("configureFsSafePython()", mappedMode);
|
|
1047
|
+
if (config.mode !== undefined) {
|
|
1048
|
+
configureFsSafeNative({ mode: config.mode });
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function getFsSafeNativeConfig() {
|
|
1052
|
+
const legacyMode = readLegacyPythonMode();
|
|
1066
1053
|
return {
|
|
1067
1054
|
mode: overrideConfig.mode ??
|
|
1068
|
-
parseMode(process.env.
|
|
1069
|
-
parseMode(process.env.
|
|
1055
|
+
parseMode(process.env.FS_SAFE_NATIVE_MODE) ??
|
|
1056
|
+
parseMode(process.env.OPENCLAW_FS_SAFE_NATIVE_MODE) ??
|
|
1057
|
+
legacyMode ??
|
|
1070
1058
|
"auto",
|
|
1071
|
-
|
|
1072
|
-
process.env.FS_SAFE_PYTHON ??
|
|
1073
|
-
process.env.OPENCLAW_FS_SAFE_PYTHON ??
|
|
1074
|
-
process.env.OPENCLAW_PINNED_PYTHON ??
|
|
1075
|
-
process.env.OPENCLAW_PINNED_WRITE_PYTHON,
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
function canFallbackFromPythonError(error) {
|
|
1079
|
-
const code = error instanceof Error && "code" in error ? error.code : undefined;
|
|
1080
|
-
return (getFsSafePythonConfig().mode !== "require" &&
|
|
1081
|
-
(code === "helper-unavailable" || code === "unsupported-platform"));
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-python.js
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const PINNED_PYTHON_WORKER_SOURCE = String.raw `
|
|
1090
|
-
import base64, errno, json, os, secrets, stat, sys
|
|
1091
|
-
DIR_FLAGS = os.O_RDONLY
|
|
1092
|
-
if hasattr(os, "O_DIRECTORY"): DIR_FLAGS |= os.O_DIRECTORY
|
|
1093
|
-
if hasattr(os, "O_NOFOLLOW"): DIR_FLAGS |= os.O_NOFOLLOW
|
|
1094
|
-
READ_FLAGS = os.O_RDONLY
|
|
1095
|
-
if hasattr(os, "O_NONBLOCK"): READ_FLAGS |= os.O_NONBLOCK
|
|
1096
|
-
if hasattr(os, "O_NOFOLLOW"): READ_FLAGS |= os.O_NOFOLLOW
|
|
1097
|
-
WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
1098
|
-
if hasattr(os, "O_NOFOLLOW"):
|
|
1099
|
-
WRITE_FLAGS |= os.O_NOFOLLOW
|
|
1100
|
-
def split_relative(value):
|
|
1101
|
-
if value in ("", "."):
|
|
1102
|
-
return []
|
|
1103
|
-
if "\x00" in value or value.startswith("/") or value.startswith("//"):
|
|
1104
|
-
raise OSError(errno.EPERM, "invalid relative path")
|
|
1105
|
-
if value.startswith("..\\"):
|
|
1106
|
-
raise OSError(errno.EPERM, "path traversal is not allowed")
|
|
1107
|
-
parts = [part for part in value.split("/") if part and part != "."]
|
|
1108
|
-
for part in parts:
|
|
1109
|
-
if part == "..":
|
|
1110
|
-
raise OSError(errno.EPERM, "path traversal is not allowed")
|
|
1111
|
-
return parts
|
|
1112
|
-
def open_dir(path_value, dir_fd=None):
|
|
1113
|
-
return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
|
|
1114
|
-
def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
1115
|
-
current_fd = os.dup(root_fd)
|
|
1116
|
-
try:
|
|
1117
|
-
for segment in segments:
|
|
1118
|
-
try:
|
|
1119
|
-
next_fd = open_dir(segment, dir_fd=current_fd)
|
|
1120
|
-
except FileNotFoundError:
|
|
1121
|
-
if not mkdir_enabled:
|
|
1122
|
-
raise
|
|
1123
|
-
os.mkdir(segment, 0o777, dir_fd=current_fd)
|
|
1124
|
-
next_fd = open_dir(segment, dir_fd=current_fd)
|
|
1125
|
-
os.close(current_fd)
|
|
1126
|
-
current_fd = next_fd
|
|
1127
|
-
return current_fd
|
|
1128
|
-
except Exception:
|
|
1129
|
-
os.close(current_fd)
|
|
1130
|
-
raise
|
|
1131
|
-
def parent_and_basename(root_fd, relative):
|
|
1132
|
-
segments = split_relative(relative)
|
|
1133
|
-
if not segments:
|
|
1134
|
-
raise OSError(errno.EPERM, "operation requires a non-root path")
|
|
1135
|
-
parent_fd = walk_dir(root_fd, segments[:-1])
|
|
1136
|
-
return parent_fd, segments[-1]
|
|
1137
|
-
def encode_stat(st):
|
|
1138
|
-
mode = st.st_mode
|
|
1139
|
-
return {
|
|
1140
|
-
"dev": st.st_dev,
|
|
1141
|
-
"gid": st.st_gid,
|
|
1142
|
-
"ino": st.st_ino,
|
|
1143
|
-
"isDirectory": stat.S_ISDIR(mode),
|
|
1144
|
-
"isFile": stat.S_ISREG(mode),
|
|
1145
|
-
"isSymbolicLink": stat.S_ISLNK(mode),
|
|
1146
|
-
"mode": mode,
|
|
1147
|
-
"mtimeMs": st.st_mtime * 1000,
|
|
1148
|
-
"nlink": st.st_nlink,
|
|
1149
|
-
"size": st.st_size,
|
|
1150
|
-
"uid": st.st_uid,
|
|
1151
|
-
}
|
|
1152
|
-
def reject_unsafe_endpoint(st):
|
|
1153
|
-
mode = st.st_mode
|
|
1154
|
-
if stat.S_ISLNK(mode):
|
|
1155
|
-
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
|
|
1156
|
-
if stat.S_ISREG(mode) and st.st_nlink > 1:
|
|
1157
|
-
raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
|
|
1158
|
-
def copy_bytes(source_fd, dest_fd):
|
|
1159
|
-
while True:
|
|
1160
|
-
chunk = os.read(source_fd, 65536)
|
|
1161
|
-
if not chunk:
|
|
1162
|
-
break
|
|
1163
|
-
view = memoryview(chunk)
|
|
1164
|
-
while view:
|
|
1165
|
-
written = os.write(dest_fd, view)
|
|
1166
|
-
if written <= 0:
|
|
1167
|
-
raise OSError(errno.EIO, "short write")
|
|
1168
|
-
view = view[written:]
|
|
1169
|
-
def write_all(fd, data):
|
|
1170
|
-
view = memoryview(data)
|
|
1171
|
-
while view:
|
|
1172
|
-
written = os.write(fd, view)
|
|
1173
|
-
if written <= 0:
|
|
1174
|
-
raise OSError(errno.EIO, "short write")
|
|
1175
|
-
view = view[written:]
|
|
1176
|
-
def fsync_best_effort(fd):
|
|
1177
|
-
try: os.fsync(fd)
|
|
1178
|
-
except OSError as error:
|
|
1179
|
-
if error.errno != errno.EPERM: raise
|
|
1180
|
-
def link_unsupported(exc):
|
|
1181
|
-
unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
|
|
1182
|
-
return getattr(exc, "errno", None) in unsupported
|
|
1183
|
-
def link_no_replace(name, new_name, source_fd, target_fd):
|
|
1184
|
-
linked = False
|
|
1185
|
-
try:
|
|
1186
|
-
os.link(name, new_name, src_dir_fd=source_fd, dst_dir_fd=target_fd, follow_symlinks=False)
|
|
1187
|
-
linked = True
|
|
1188
|
-
os.unlink(name, dir_fd=source_fd)
|
|
1189
|
-
except Exception:
|
|
1190
|
-
if linked:
|
|
1191
|
-
try: os.unlink(new_name, dir_fd=target_fd)
|
|
1192
|
-
except FileNotFoundError: pass
|
|
1193
|
-
raise
|
|
1194
|
-
os.fsync(source_fd)
|
|
1195
|
-
if source_fd != target_fd:
|
|
1196
|
-
os.fsync(target_fd)
|
|
1197
|
-
def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
|
|
1198
|
-
source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
|
|
1199
|
-
dest_fd = None; success = False; dest_stat = None
|
|
1200
|
-
try:
|
|
1201
|
-
if expected is not None:
|
|
1202
|
-
source_stat = os.fstat(source_fd)
|
|
1203
|
-
if source_stat.st_dev != expected.st_dev or source_stat.st_ino != expected.st_ino:
|
|
1204
|
-
raise RuntimeError("fs-safe-source-mismatch")
|
|
1205
|
-
dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
|
|
1206
|
-
copy_bytes(source_fd, dest_fd)
|
|
1207
|
-
os.fsync(dest_fd)
|
|
1208
|
-
dest_stat = os.fstat(dest_fd)
|
|
1209
|
-
success = True
|
|
1210
|
-
finally:
|
|
1211
|
-
os.close(source_fd)
|
|
1212
|
-
if dest_fd is not None:
|
|
1213
|
-
os.close(dest_fd)
|
|
1214
|
-
if dest_fd is not None and not success:
|
|
1215
|
-
try: os.unlink(basename, dir_fd=target_parent_fd)
|
|
1216
|
-
except FileNotFoundError: pass
|
|
1217
|
-
if unlink_source:
|
|
1218
|
-
try:
|
|
1219
|
-
os.unlink(source_name, dir_fd=source_parent_fd)
|
|
1220
|
-
except Exception:
|
|
1221
|
-
try: os.unlink(basename, dir_fd=target_parent_fd)
|
|
1222
|
-
except FileNotFoundError: pass
|
|
1223
|
-
raise
|
|
1224
|
-
return dest_stat
|
|
1225
|
-
def same_identity(left, right):
|
|
1226
|
-
return left.st_dev == right.st_dev and left.st_ino == right.st_ino
|
|
1227
|
-
def verify_temp_name(parent_fd, temp_name, expected_stat):
|
|
1228
|
-
current_stat = os.lstat(temp_name, dir_fd=parent_fd)
|
|
1229
|
-
if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
|
|
1230
|
-
raise RuntimeError("fs-safe-temp-mismatch")
|
|
1231
|
-
def verify_committed_temp(parent_fd, basename, expected_stat):
|
|
1232
|
-
final_stat = os.lstat(basename, dir_fd=parent_fd)
|
|
1233
|
-
if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
|
|
1234
|
-
return final_stat
|
|
1235
|
-
try: os.unlink(basename, dir_fd=parent_fd)
|
|
1236
|
-
except FileNotFoundError: pass
|
|
1237
|
-
raise RuntimeError("fs-safe-temp-mismatch")
|
|
1238
|
-
def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
|
|
1239
|
-
verify_temp_name(parent_fd, temp_name, expected_stat)
|
|
1240
|
-
if overwrite:
|
|
1241
|
-
os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
|
|
1242
|
-
return verify_committed_temp(parent_fd, basename, expected_stat)
|
|
1243
|
-
else:
|
|
1244
|
-
try:
|
|
1245
|
-
os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
|
|
1246
|
-
final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
|
|
1247
|
-
os.unlink(temp_name, dir_fd=parent_fd)
|
|
1248
|
-
return final_stat
|
|
1249
|
-
except OSError as exc:
|
|
1250
|
-
if not link_unsupported(exc):
|
|
1251
|
-
raise
|
|
1252
|
-
return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
|
|
1253
|
-
def assert_expected_root(root_fd, payload):
|
|
1254
|
-
if "rootDev" in payload or "rootIno" in payload:
|
|
1255
|
-
root_stat = os.fstat(root_fd)
|
|
1256
|
-
if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
|
|
1257
|
-
raise RuntimeError("fs-safe-root-mismatch")
|
|
1258
|
-
def stat_path(root_fd, payload):
|
|
1259
|
-
relative = payload.get("relativePath", "")
|
|
1260
|
-
segments = split_relative(relative)
|
|
1261
|
-
if not segments:
|
|
1262
|
-
return encode_stat(os.fstat(root_fd))
|
|
1263
|
-
parent_fd, basename = parent_and_basename(root_fd, relative)
|
|
1264
|
-
try:
|
|
1265
|
-
st = os.lstat(basename, dir_fd=parent_fd)
|
|
1266
|
-
if payload.get("rejectSymlink", True) and stat.S_ISLNK(st.st_mode):
|
|
1267
|
-
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
|
|
1268
|
-
return encode_stat(st)
|
|
1269
|
-
finally:
|
|
1270
|
-
os.close(parent_fd)
|
|
1271
|
-
def readdir_path(root_fd, payload):
|
|
1272
|
-
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
|
|
1273
|
-
try:
|
|
1274
|
-
names = sorted(os.listdir(dir_fd))
|
|
1275
|
-
if not payload.get("withFileTypes", False):
|
|
1276
|
-
return names
|
|
1277
|
-
entries = []
|
|
1278
|
-
for name in names:
|
|
1279
|
-
st = os.lstat(name, dir_fd=dir_fd)
|
|
1280
|
-
entry = encode_stat(st)
|
|
1281
|
-
entry["name"] = name
|
|
1282
|
-
entries.append(entry)
|
|
1283
|
-
return entries
|
|
1284
|
-
finally:
|
|
1285
|
-
os.close(dir_fd)
|
|
1286
|
-
def mkdirp_path(root_fd, payload):
|
|
1287
|
-
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
|
|
1288
|
-
os.close(dir_fd); return None
|
|
1289
|
-
def remove_tree(parent_fd, basename):
|
|
1290
|
-
st = os.lstat(basename, dir_fd=parent_fd)
|
|
1291
|
-
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
|
|
1292
|
-
dir_fd = open_dir(basename, dir_fd=parent_fd)
|
|
1293
|
-
try:
|
|
1294
|
-
for child in os.listdir(dir_fd):
|
|
1295
|
-
remove_tree(dir_fd, child)
|
|
1296
|
-
finally:
|
|
1297
|
-
os.close(dir_fd)
|
|
1298
|
-
os.rmdir(basename, dir_fd=parent_fd)
|
|
1299
|
-
else:
|
|
1300
|
-
os.unlink(basename, dir_fd=parent_fd)
|
|
1301
|
-
def remove_path(root_fd, payload):
|
|
1302
|
-
parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
|
|
1303
|
-
try:
|
|
1304
|
-
try:
|
|
1305
|
-
st = os.lstat(basename, dir_fd=parent_fd)
|
|
1306
|
-
except FileNotFoundError:
|
|
1307
|
-
if payload.get("force", True):
|
|
1308
|
-
return None
|
|
1309
|
-
raise
|
|
1310
|
-
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
|
|
1311
|
-
if payload.get("recursive", False):
|
|
1312
|
-
remove_tree(parent_fd, basename)
|
|
1313
|
-
else:
|
|
1314
|
-
os.rmdir(basename, dir_fd=parent_fd)
|
|
1315
|
-
else:
|
|
1316
|
-
os.unlink(basename, dir_fd=parent_fd)
|
|
1317
|
-
return None
|
|
1318
|
-
finally:
|
|
1319
|
-
os.close(parent_fd)
|
|
1320
|
-
|
|
1321
|
-
def rename_path(root_fd, payload):
|
|
1322
|
-
from_parent_fd, from_base = parent_and_basename(root_fd, payload["from"])
|
|
1323
|
-
to_parent_fd, to_base = parent_and_basename(root_fd, payload["to"])
|
|
1324
|
-
try:
|
|
1325
|
-
from_stat = os.lstat(from_base, dir_fd=from_parent_fd)
|
|
1326
|
-
reject_unsafe_endpoint(from_stat)
|
|
1327
|
-
overwrite = payload.get("overwrite", True)
|
|
1328
|
-
if not overwrite and stat.S_ISREG(from_stat.st_mode):
|
|
1329
|
-
try:
|
|
1330
|
-
link_no_replace(from_base, to_base, from_parent_fd, to_parent_fd)
|
|
1331
|
-
except OSError as exc:
|
|
1332
|
-
if not link_unsupported(exc):
|
|
1333
|
-
raise
|
|
1334
|
-
copy_file_no_replace(from_parent_fd, from_base, to_parent_fd, to_base, stat.S_IMODE(from_stat.st_mode), from_stat, True)
|
|
1335
|
-
return None
|
|
1336
|
-
if not overwrite and stat.S_ISDIR(from_stat.st_mode):
|
|
1337
|
-
raise RuntimeError("fs-safe-directory-noreplace-unsupported")
|
|
1338
|
-
if not overwrite:
|
|
1339
|
-
try:
|
|
1340
|
-
os.lstat(to_base, dir_fd=to_parent_fd)
|
|
1341
|
-
raise FileExistsError(errno.EEXIST, "destination exists", to_base)
|
|
1342
|
-
except FileNotFoundError:
|
|
1343
|
-
pass
|
|
1344
|
-
os.rename(from_base, to_base, src_dir_fd=from_parent_fd, dst_dir_fd=to_parent_fd)
|
|
1345
|
-
os.fsync(from_parent_fd)
|
|
1346
|
-
if from_parent_fd != to_parent_fd:
|
|
1347
|
-
os.fsync(to_parent_fd)
|
|
1348
|
-
return None
|
|
1349
|
-
finally:
|
|
1350
|
-
os.close(from_parent_fd)
|
|
1351
|
-
os.close(to_parent_fd)
|
|
1352
|
-
|
|
1353
|
-
def create_temp_file(parent_fd, basename, mode):
|
|
1354
|
-
prefix = "." + basename + "."
|
|
1355
|
-
for _ in range(128):
|
|
1356
|
-
candidate = prefix + secrets.token_hex(6) + ".tmp"
|
|
1357
|
-
try:
|
|
1358
|
-
fd = os.open(candidate, WRITE_FLAGS, mode, dir_fd=parent_fd)
|
|
1359
|
-
return candidate, fd
|
|
1360
|
-
except FileExistsError:
|
|
1361
|
-
continue
|
|
1362
|
-
raise RuntimeError("failed to allocate pinned temp file")
|
|
1363
|
-
|
|
1364
|
-
def write_path(root_fd, payload):
|
|
1365
|
-
parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
|
|
1366
|
-
temp_fd = None
|
|
1367
|
-
temp_name = None
|
|
1368
|
-
basename = payload["basename"]
|
|
1369
|
-
mode = int(payload.get("mode", 0o600))
|
|
1370
|
-
overwrite = bool(payload.get("overwrite", True))
|
|
1371
|
-
max_bytes = int(payload.get("maxBytes", -1))
|
|
1372
|
-
data = base64.b64decode(payload.get("base64", ""))
|
|
1373
|
-
try:
|
|
1374
|
-
if max_bytes >= 0 and len(data) > max_bytes:
|
|
1375
|
-
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, len(data)))
|
|
1376
|
-
if not overwrite:
|
|
1377
|
-
try:
|
|
1378
|
-
os.lstat(basename, dir_fd=parent_fd)
|
|
1379
|
-
raise FileExistsError(errno.EEXIST, "destination exists", basename)
|
|
1380
|
-
except FileNotFoundError:
|
|
1381
|
-
pass
|
|
1382
|
-
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
1383
|
-
os.fchmod(temp_fd, mode)
|
|
1384
|
-
write_all(temp_fd, data)
|
|
1385
|
-
fsync_best_effort(temp_fd)
|
|
1386
|
-
temp_stat = os.fstat(temp_fd)
|
|
1387
|
-
os.close(temp_fd)
|
|
1388
|
-
temp_fd = None
|
|
1389
|
-
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
1390
|
-
temp_name = None
|
|
1391
|
-
fsync_best_effort(parent_fd)
|
|
1392
|
-
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
1393
|
-
finally:
|
|
1394
|
-
if temp_fd is not None:
|
|
1395
|
-
os.close(temp_fd)
|
|
1396
|
-
if temp_name is not None:
|
|
1397
|
-
try:
|
|
1398
|
-
os.unlink(temp_name, dir_fd=parent_fd)
|
|
1399
|
-
except FileNotFoundError:
|
|
1400
|
-
pass
|
|
1401
|
-
os.close(parent_fd)
|
|
1402
|
-
|
|
1403
|
-
def copy_path(root_fd, payload):
|
|
1404
|
-
source_fd = os.open(payload["sourcePath"], READ_FLAGS)
|
|
1405
|
-
parent_fd = None
|
|
1406
|
-
temp_fd = None
|
|
1407
|
-
temp_name = None
|
|
1408
|
-
try:
|
|
1409
|
-
source_stat = os.fstat(source_fd)
|
|
1410
|
-
if not stat.S_ISREG(source_stat.st_mode):
|
|
1411
|
-
raise RuntimeError("fs-safe-not-file")
|
|
1412
|
-
if source_stat.st_dev != int(payload["sourceDev"]) or source_stat.st_ino != int(payload["sourceIno"]):
|
|
1413
|
-
raise RuntimeError("fs-safe-source-mismatch")
|
|
1414
|
-
basename = payload["basename"]
|
|
1415
|
-
mode = int(payload.get("mode", 0o600))
|
|
1416
|
-
overwrite = bool(payload.get("overwrite", True))
|
|
1417
|
-
max_bytes = int(payload.get("maxBytes", -1))
|
|
1418
|
-
if max_bytes >= 0 and source_stat.st_size > max_bytes:
|
|
1419
|
-
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
|
|
1420
|
-
parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
|
|
1421
|
-
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
1422
|
-
os.fchmod(temp_fd, mode)
|
|
1423
|
-
written_bytes = 0
|
|
1424
|
-
while True:
|
|
1425
|
-
chunk = os.read(source_fd, 65536)
|
|
1426
|
-
if not chunk:
|
|
1427
|
-
break
|
|
1428
|
-
written_bytes += len(chunk)
|
|
1429
|
-
if max_bytes >= 0 and written_bytes > max_bytes:
|
|
1430
|
-
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, written_bytes))
|
|
1431
|
-
view = memoryview(chunk)
|
|
1432
|
-
while view:
|
|
1433
|
-
written = os.write(temp_fd, view)
|
|
1434
|
-
if written <= 0:
|
|
1435
|
-
raise OSError(errno.EIO, "short write")
|
|
1436
|
-
view = view[written:]
|
|
1437
|
-
os.fsync(temp_fd)
|
|
1438
|
-
temp_stat = os.fstat(temp_fd)
|
|
1439
|
-
os.close(temp_fd)
|
|
1440
|
-
temp_fd = None
|
|
1441
|
-
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
1442
|
-
temp_name = None
|
|
1443
|
-
os.fsync(parent_fd)
|
|
1444
|
-
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
1445
|
-
finally:
|
|
1446
|
-
os.close(source_fd)
|
|
1447
|
-
if temp_fd is not None:
|
|
1448
|
-
os.close(temp_fd)
|
|
1449
|
-
if temp_name is not None and parent_fd is not None:
|
|
1450
|
-
try:
|
|
1451
|
-
os.unlink(temp_name, dir_fd=parent_fd)
|
|
1452
|
-
except FileNotFoundError:
|
|
1453
|
-
pass
|
|
1454
|
-
if parent_fd is not None:
|
|
1455
|
-
os.close(parent_fd)
|
|
1456
|
-
|
|
1457
|
-
def run_operation(operation, root_path, payload):
|
|
1458
|
-
root_fd = open_dir(root_path)
|
|
1459
|
-
try:
|
|
1460
|
-
assert_expected_root(root_fd, payload)
|
|
1461
|
-
if operation == "stat":
|
|
1462
|
-
return stat_path(root_fd, payload)
|
|
1463
|
-
if operation == "readdir":
|
|
1464
|
-
return readdir_path(root_fd, payload)
|
|
1465
|
-
if operation == "mkdirp":
|
|
1466
|
-
return mkdirp_path(root_fd, payload)
|
|
1467
|
-
if operation == "remove":
|
|
1468
|
-
return remove_path(root_fd, payload)
|
|
1469
|
-
if operation == "rename":
|
|
1470
|
-
return rename_path(root_fd, payload)
|
|
1471
|
-
if operation == "write":
|
|
1472
|
-
return write_path(root_fd, payload)
|
|
1473
|
-
if operation == "copy":
|
|
1474
|
-
return copy_path(root_fd, payload)
|
|
1475
|
-
raise RuntimeError("unknown operation: " + operation)
|
|
1476
|
-
finally:
|
|
1477
|
-
os.close(root_fd)
|
|
1478
|
-
|
|
1479
|
-
for line in sys.stdin:
|
|
1480
|
-
try:
|
|
1481
|
-
request = json.loads(line)
|
|
1482
|
-
result = run_operation(request["operation"], request["rootPath"], request.get("payload") or {})
|
|
1483
|
-
response = {"id": request["id"], "ok": True, "result": result}
|
|
1484
|
-
except Exception as exc:
|
|
1485
|
-
response = {
|
|
1486
|
-
"id": request.get("id") if isinstance(locals().get("request"), dict) else None,
|
|
1487
|
-
"ok": False,
|
|
1488
|
-
"code": exc.__class__.__name__,
|
|
1489
|
-
"errno": getattr(exc, "errno", None),
|
|
1490
|
-
"message": str(exc),
|
|
1491
|
-
}
|
|
1492
|
-
print(json.dumps(response, separators=(",", ":")), flush=True)
|
|
1493
|
-
`;
|
|
1494
|
-
let nextRequestId = 1;
|
|
1495
|
-
let worker = null;
|
|
1496
|
-
function __resetPinnedPythonWorkerForTest() {
|
|
1497
|
-
const currentWorker = worker;
|
|
1498
|
-
worker = null;
|
|
1499
|
-
if (!currentWorker) {
|
|
1500
|
-
return;
|
|
1501
|
-
}
|
|
1502
|
-
currentWorker.pending.clear();
|
|
1503
|
-
currentWorker.child.kill("SIGTERM");
|
|
1059
|
+
};
|
|
1504
1060
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1061
|
+
function __resetFsSafeNativeConfigForTest() {
|
|
1062
|
+
overrideConfig = {};
|
|
1063
|
+
legacyWarningEmitted = false;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native.js
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
const native_require = (0,external_node_module_namespaceObject.createRequire)(import.meta.url);
|
|
1074
|
+
const bundledNativeDirectory = (0,external_node_url_namespaceObject.fileURLToPath)(new URL("../dist/native/", import.meta.url));
|
|
1075
|
+
let native_binding;
|
|
1076
|
+
let native_loadError;
|
|
1077
|
+
let attempted = false;
|
|
1078
|
+
let loadBinding = loadBundledBinding;
|
|
1079
|
+
function isMuslFilename(file) {
|
|
1080
|
+
return file.includes("libc.musl-") || file.includes("ld-musl-");
|
|
1081
|
+
}
|
|
1082
|
+
function isMuslFromReport() {
|
|
1511
1083
|
try {
|
|
1512
|
-
|
|
1513
|
-
|
|
1084
|
+
if (!process.report || typeof process.report.getReport !== "function")
|
|
1085
|
+
return undefined;
|
|
1086
|
+
const report = process.report.getReport();
|
|
1087
|
+
if (report.header?.glibcVersionRuntime)
|
|
1088
|
+
return false;
|
|
1089
|
+
if (report.sharedObjects?.some(isMuslFilename))
|
|
1090
|
+
return true;
|
|
1514
1091
|
}
|
|
1515
1092
|
catch {
|
|
1516
|
-
|
|
1093
|
+
// Continue with filesystem and ELF inspection.
|
|
1517
1094
|
}
|
|
1095
|
+
return undefined;
|
|
1518
1096
|
}
|
|
1519
|
-
function
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1097
|
+
function isMuslFromFilesystem() {
|
|
1098
|
+
for (const directory of ["/lib", "/usr/lib"]) {
|
|
1099
|
+
try {
|
|
1100
|
+
if ((0,external_node_fs_namespaceObject.readdirSync)(directory).some(isMuslFilename))
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
catch {
|
|
1104
|
+
// A missing or unreadable conventional library directory is inconclusive.
|
|
1527
1105
|
}
|
|
1528
1106
|
}
|
|
1529
|
-
return
|
|
1107
|
+
return undefined;
|
|
1530
1108
|
}
|
|
1531
|
-
function
|
|
1532
|
-
if (
|
|
1533
|
-
|
|
1109
|
+
function readUInt(buffer, offset, bytes, littleEndian) {
|
|
1110
|
+
if (offset < 0 || offset + bytes > buffer.length)
|
|
1111
|
+
return undefined;
|
|
1112
|
+
if (bytes === 2)
|
|
1113
|
+
return littleEndian ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
|
|
1114
|
+
if (bytes === 4)
|
|
1115
|
+
return littleEndian ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
|
|
1116
|
+
const value = littleEndian ? buffer.readBigUInt64LE(offset) : buffer.readBigUInt64BE(offset);
|
|
1117
|
+
return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : undefined;
|
|
1118
|
+
}
|
|
1119
|
+
function isMuslFromElfInterpreter() {
|
|
1120
|
+
let fd;
|
|
1121
|
+
try {
|
|
1122
|
+
fd = (0,external_node_fs_namespaceObject.openSync)(process.execPath, "r");
|
|
1123
|
+
const header = Buffer.alloc(64);
|
|
1124
|
+
if ((0,external_node_fs_namespaceObject.readSync)(fd, header, 0, header.length, 0) < 52)
|
|
1125
|
+
return undefined;
|
|
1126
|
+
if (!header.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])))
|
|
1127
|
+
return undefined;
|
|
1128
|
+
const elfClass = header[4];
|
|
1129
|
+
const dataEncoding = header[5];
|
|
1130
|
+
if ((elfClass !== 1 && elfClass !== 2) || (dataEncoding !== 1 && dataEncoding !== 2)) {
|
|
1131
|
+
return undefined;
|
|
1132
|
+
}
|
|
1133
|
+
const littleEndian = dataEncoding === 1;
|
|
1134
|
+
const is64Bit = elfClass === 2;
|
|
1135
|
+
const tableOffset = readUInt(header, is64Bit ? 32 : 28, is64Bit ? 8 : 4, littleEndian);
|
|
1136
|
+
const entrySize = readUInt(header, is64Bit ? 54 : 42, 2, littleEndian);
|
|
1137
|
+
const entryCount = readUInt(header, is64Bit ? 56 : 44, 2, littleEndian);
|
|
1138
|
+
if (tableOffset === undefined ||
|
|
1139
|
+
entrySize === undefined ||
|
|
1140
|
+
entryCount === undefined ||
|
|
1141
|
+
entrySize < (is64Bit ? 56 : 32) ||
|
|
1142
|
+
entryCount > 1024) {
|
|
1143
|
+
return undefined;
|
|
1144
|
+
}
|
|
1145
|
+
const programHeader = Buffer.alloc(entrySize);
|
|
1146
|
+
for (let index = 0; index < entryCount; index += 1) {
|
|
1147
|
+
const offset = tableOffset + index * entrySize;
|
|
1148
|
+
if ((0,external_node_fs_namespaceObject.readSync)(fd, programHeader, 0, entrySize, offset) !== entrySize)
|
|
1149
|
+
return undefined;
|
|
1150
|
+
if (readUInt(programHeader, 0, 4, littleEndian) !== 3)
|
|
1151
|
+
continue;
|
|
1152
|
+
const interpreterOffset = readUInt(programHeader, is64Bit ? 8 : 4, is64Bit ? 8 : 4, littleEndian);
|
|
1153
|
+
const interpreterSize = readUInt(programHeader, is64Bit ? 32 : 16, is64Bit ? 8 : 4, littleEndian);
|
|
1154
|
+
if (interpreterOffset === undefined ||
|
|
1155
|
+
interpreterSize === undefined ||
|
|
1156
|
+
interpreterSize < 1 ||
|
|
1157
|
+
interpreterSize > 4096) {
|
|
1158
|
+
return undefined;
|
|
1159
|
+
}
|
|
1160
|
+
const interpreter = Buffer.alloc(interpreterSize);
|
|
1161
|
+
if ((0,external_node_fs_namespaceObject.readSync)(fd, interpreter, 0, interpreterSize, interpreterOffset) !== interpreterSize) {
|
|
1162
|
+
return undefined;
|
|
1163
|
+
}
|
|
1164
|
+
return isMuslFilename(interpreter.toString("utf8"));
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
catch {
|
|
1168
|
+
return undefined;
|
|
1534
1169
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1170
|
+
finally {
|
|
1171
|
+
if (fd !== undefined) {
|
|
1172
|
+
try {
|
|
1173
|
+
(0,external_node_fs_namespaceObject.closeSync)(fd);
|
|
1174
|
+
}
|
|
1175
|
+
catch {
|
|
1176
|
+
// Best-effort detection must never turn close failure into import failure.
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1537
1179
|
}
|
|
1180
|
+
return undefined;
|
|
1538
1181
|
}
|
|
1539
|
-
function
|
|
1540
|
-
if (
|
|
1182
|
+
function isMusl() {
|
|
1183
|
+
if (process.platform !== "linux")
|
|
1541
1184
|
return false;
|
|
1185
|
+
for (const detector of [isMuslFromReport, isMuslFromFilesystem, isMuslFromElfInterpreter]) {
|
|
1186
|
+
const result = detector();
|
|
1187
|
+
if (result !== undefined)
|
|
1188
|
+
return result;
|
|
1542
1189
|
}
|
|
1543
|
-
|
|
1544
|
-
return
|
|
1545
|
-
maybeErrno.syscall.startsWith("spawn") &&
|
|
1546
|
-
["EACCES", "ENOENT", "ENOEXEC"].includes(maybeErrno.code ?? ""));
|
|
1190
|
+
// Unknown Linux libc: try the glibc binary and let its require fail normally.
|
|
1191
|
+
return false;
|
|
1547
1192
|
}
|
|
1548
|
-
function
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
if (
|
|
1556
|
-
|
|
1557
|
-
return new errors_FsSafeError("too-large", `file exceeds limit of ${limit} bytes (got at least ${got})`);
|
|
1558
|
-
}
|
|
1559
|
-
if (message.includes("fs-safe-not-file")) {
|
|
1560
|
-
return new errors_FsSafeError("not-file", "not a file");
|
|
1561
|
-
}
|
|
1562
|
-
if (message.includes("fs-safe-source-mismatch")) {
|
|
1563
|
-
return new errors_FsSafeError("path-mismatch", "source path changed during copy");
|
|
1193
|
+
function targetFor(platform, arch, musl) {
|
|
1194
|
+
if (platform === "win32" && arch === "x64")
|
|
1195
|
+
return "win32-x64-msvc";
|
|
1196
|
+
if (platform === "darwin" && arch === "x64")
|
|
1197
|
+
return "darwin-x64";
|
|
1198
|
+
if (platform === "darwin" && arch === "arm64")
|
|
1199
|
+
return "darwin-arm64";
|
|
1200
|
+
if (platform === "linux" && (arch === "x64" || arch === "arm64")) {
|
|
1201
|
+
return `linux-${arch}-${musl ? "musl" : "gnu"}`;
|
|
1564
1202
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
if (code === "FileNotFoundError" || errno === 2) {
|
|
1575
|
-
return new errors_FsSafeError("not-found", "file not found");
|
|
1576
|
-
}
|
|
1577
|
-
if (code === "FileExistsError" || errno === 17) {
|
|
1578
|
-
return new errors_FsSafeError("already-exists", message);
|
|
1579
|
-
}
|
|
1580
|
-
if (errno === 39) {
|
|
1581
|
-
return new errors_FsSafeError("not-empty", "directory is not empty");
|
|
1203
|
+
return undefined;
|
|
1204
|
+
}
|
|
1205
|
+
function bundledTarget() {
|
|
1206
|
+
return targetFor(process.platform, process.arch, isMusl());
|
|
1207
|
+
}
|
|
1208
|
+
function loadBundledBinding() {
|
|
1209
|
+
const target = bundledTarget();
|
|
1210
|
+
if (!target) {
|
|
1211
|
+
throw new Error(`Unsupported OS or architecture: ${process.platform}-${process.arch}`);
|
|
1582
1212
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1213
|
+
return native_require((0,external_node_path_namespaceObject.join)(bundledNativeDirectory, target, "fs-safe-native.node"));
|
|
1214
|
+
}
|
|
1215
|
+
function __loadBundledNativeForTest() {
|
|
1216
|
+
return loadBundledBinding();
|
|
1217
|
+
}
|
|
1218
|
+
function __nativeLoaderDetectorsForTest() {
|
|
1219
|
+
return {
|
|
1220
|
+
report: isMuslFromReport(),
|
|
1221
|
+
filesystem: isMuslFromFilesystem(),
|
|
1222
|
+
elfInterpreter: isMuslFromElfInterpreter(),
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
function __nativeTargetForTest(platform, arch, musl = false) {
|
|
1226
|
+
return targetFor(platform, arch, musl);
|
|
1227
|
+
}
|
|
1228
|
+
function getNativeBinding() {
|
|
1229
|
+
const { mode } = getFsSafeNativeConfig();
|
|
1230
|
+
if (mode === "off")
|
|
1231
|
+
return undefined;
|
|
1232
|
+
if (!attempted) {
|
|
1233
|
+
attempted = true;
|
|
1234
|
+
try {
|
|
1235
|
+
native_binding = loadBinding();
|
|
1236
|
+
}
|
|
1237
|
+
catch (error) {
|
|
1238
|
+
native_loadError = error;
|
|
1239
|
+
}
|
|
1585
1240
|
}
|
|
1586
|
-
if (
|
|
1587
|
-
return
|
|
1241
|
+
if (native_binding)
|
|
1242
|
+
return native_binding;
|
|
1243
|
+
if (mode === "require") {
|
|
1244
|
+
throw new errors_FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
|
|
1245
|
+
cause: native_loadError,
|
|
1246
|
+
});
|
|
1588
1247
|
}
|
|
1589
|
-
return
|
|
1248
|
+
return undefined;
|
|
1590
1249
|
}
|
|
1591
|
-
function
|
|
1592
|
-
|
|
1593
|
-
|
|
1250
|
+
function requireNativeBinding() {
|
|
1251
|
+
const native = getNativeBinding();
|
|
1252
|
+
if (!native) {
|
|
1253
|
+
throw new FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
|
|
1254
|
+
cause: native_loadError,
|
|
1255
|
+
});
|
|
1594
1256
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1257
|
+
return native;
|
|
1258
|
+
}
|
|
1259
|
+
function __setNativeLoaderForTest(loader) {
|
|
1260
|
+
native_binding = undefined;
|
|
1261
|
+
native_loadError = undefined;
|
|
1262
|
+
attempted = false;
|
|
1263
|
+
loadBinding = loader;
|
|
1264
|
+
}
|
|
1265
|
+
function __resetNativeLoaderForTest() {
|
|
1266
|
+
native_binding = undefined;
|
|
1267
|
+
native_loadError = undefined;
|
|
1268
|
+
attempted = false;
|
|
1269
|
+
loadBinding = loadBundledBinding;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native-operations.js
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
function nativeOpenFlags(flags) {
|
|
1279
|
+
const closeOnExec = external_node_fs_namespaceObject.constants.O_CLOEXEC;
|
|
1280
|
+
return (flags |
|
|
1281
|
+
(closeOnExec ?? 0) |
|
|
1282
|
+
(typeof external_node_fs_namespaceObject.constants.O_NOFOLLOW === "number" ? external_node_fs_namespaceObject.constants.O_NOFOLLOW : 0));
|
|
1283
|
+
}
|
|
1284
|
+
function writeAll(fd, data) {
|
|
1285
|
+
let offset = 0;
|
|
1286
|
+
while (offset < data.byteLength) {
|
|
1287
|
+
const written = external_node_fs_namespaceObject.writeSync(fd, data, offset, data.byteLength - offset);
|
|
1288
|
+
if (written <= 0) {
|
|
1289
|
+
throw Object.assign(new Error("native file write made no progress"), { code: "EIO" });
|
|
1290
|
+
}
|
|
1291
|
+
offset += written;
|
|
1598
1292
|
}
|
|
1599
|
-
targetWorker.pending.clear();
|
|
1600
|
-
worker = null;
|
|
1601
1293
|
}
|
|
1602
|
-
function
|
|
1603
|
-
|
|
1294
|
+
function wrapNativeFd(fd, containment) {
|
|
1295
|
+
let open = true;
|
|
1296
|
+
return {
|
|
1297
|
+
fd,
|
|
1298
|
+
containment,
|
|
1299
|
+
async close() {
|
|
1300
|
+
if (open) {
|
|
1301
|
+
open = false;
|
|
1302
|
+
external_node_fs_namespaceObject.closeSync(fd);
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
async stat() {
|
|
1306
|
+
return external_node_fs_namespaceObject.fstatSync(fd);
|
|
1307
|
+
},
|
|
1308
|
+
async writeFile(data, encoding) {
|
|
1309
|
+
writeAll(fd, Buffer.isBuffer(data) ? data : Buffer.from(data, encoding ?? "utf8"));
|
|
1310
|
+
},
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
function removeNativeCreatedFileIfStillPinned(params) {
|
|
1314
|
+
if (!params.created) {
|
|
1604
1315
|
return;
|
|
1605
1316
|
}
|
|
1606
|
-
let decoded;
|
|
1607
1317
|
try {
|
|
1608
|
-
|
|
1318
|
+
const parentPathStat = external_node_fs_namespaceObject.lstatSync(params.parentPath);
|
|
1319
|
+
const parentFdStat = params.binding.fstatIdentity(params.parentFd);
|
|
1320
|
+
const targetPath = external_node_path_namespaceObject.join(params.parentPath, params.basename);
|
|
1321
|
+
const target = external_node_fs_namespaceObject.lstatSync(targetPath);
|
|
1322
|
+
if (!parentPathStat.isSymbolicLink() &&
|
|
1323
|
+
parentPathStat.dev === parentFdStat.dev &&
|
|
1324
|
+
parentPathStat.ino === parentFdStat.ino &&
|
|
1325
|
+
!target.isSymbolicLink() &&
|
|
1326
|
+
file_identity_sameFileIdentity(target, params.created)) {
|
|
1327
|
+
external_node_fs_namespaceObject.rmSync(targetPath);
|
|
1328
|
+
}
|
|
1609
1329
|
}
|
|
1610
1330
|
catch {
|
|
1611
|
-
|
|
1612
|
-
|
|
1331
|
+
// Failed cleanup leaves a fail-closed artifact instead of deleting an
|
|
1332
|
+
// unverified pathname.
|
|
1613
1333
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
const id = typeof response.id === "number" ? response.id : undefined;
|
|
1620
|
-
if (id === undefined) {
|
|
1621
|
-
return;
|
|
1334
|
+
}
|
|
1335
|
+
async function createNativeExclusiveFile(targetPath, mode) {
|
|
1336
|
+
const binding = getNativeBinding();
|
|
1337
|
+
if (!binding) {
|
|
1338
|
+
return undefined;
|
|
1622
1339
|
}
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1625
|
-
|
|
1340
|
+
const parentPath = external_node_path_namespaceObject.dirname(targetPath);
|
|
1341
|
+
const basename = external_node_path_namespaceObject.basename(targetPath);
|
|
1342
|
+
const parent = await promises_namespaceObject.open(parentPath, external_node_fs_namespaceObject.constants.O_RDONLY |
|
|
1343
|
+
(typeof external_node_fs_namespaceObject.constants.O_DIRECTORY === "number" ? external_node_fs_namespaceObject.constants.O_DIRECTORY : 0));
|
|
1344
|
+
let fd;
|
|
1345
|
+
let created;
|
|
1346
|
+
try {
|
|
1347
|
+
const opened = binding.openBeneath(parent.fd, basename, nativeOpenFlags(external_node_fs_namespaceObject.constants.O_WRONLY | external_node_fs_namespaceObject.constants.O_CREAT | external_node_fs_namespaceObject.constants.O_EXCL));
|
|
1348
|
+
fd = opened.fd;
|
|
1349
|
+
external_node_fs_namespaceObject.fchmodSync(fd, mode);
|
|
1350
|
+
created = external_node_fs_namespaceObject.fstatSync(fd);
|
|
1351
|
+
return wrapNativeFd(fd, opened.containment);
|
|
1626
1352
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1353
|
+
catch (error) {
|
|
1354
|
+
if (fd !== undefined) {
|
|
1355
|
+
try {
|
|
1356
|
+
external_node_fs_namespaceObject.closeSync(fd);
|
|
1357
|
+
}
|
|
1358
|
+
catch {
|
|
1359
|
+
// Preserve the original error.
|
|
1360
|
+
}
|
|
1361
|
+
removeNativeCreatedFileIfStillPinned({
|
|
1362
|
+
binding,
|
|
1363
|
+
parentPath,
|
|
1364
|
+
parentFd: parent.fd,
|
|
1365
|
+
basename,
|
|
1366
|
+
created,
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
throw error;
|
|
1630
1370
|
}
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
return;
|
|
1371
|
+
finally {
|
|
1372
|
+
await parent.close().catch(() => undefined);
|
|
1634
1373
|
}
|
|
1635
|
-
pending.reject(mapWorkerError(decoded));
|
|
1636
1374
|
}
|
|
1637
|
-
function
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
return worker;
|
|
1375
|
+
function syncNativeFileBestEffort(fd) {
|
|
1376
|
+
try {
|
|
1377
|
+
external_node_fs_namespaceObject.fsyncSync(fd);
|
|
1641
1378
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
const currentWorker = { child, pending: new Map(), stderr: "", stdoutBuffer: "" };
|
|
1646
|
-
worker = currentWorker;
|
|
1647
|
-
child.stdout.setEncoding("utf8");
|
|
1648
|
-
child.stderr.setEncoding("utf8");
|
|
1649
|
-
child.stdout.on("data", (chunk) => {
|
|
1650
|
-
if (worker !== currentWorker) {
|
|
1651
|
-
return;
|
|
1652
|
-
}
|
|
1653
|
-
currentWorker.stdoutBuffer += chunk;
|
|
1654
|
-
for (;;) {
|
|
1655
|
-
const newline = currentWorker.stdoutBuffer.indexOf("\n");
|
|
1656
|
-
if (newline < 0) {
|
|
1657
|
-
break;
|
|
1658
|
-
}
|
|
1659
|
-
const line = currentWorker.stdoutBuffer.slice(0, newline);
|
|
1660
|
-
currentWorker.stdoutBuffer = currentWorker.stdoutBuffer.slice(newline + 1);
|
|
1661
|
-
handleWorkerLine(currentWorker, line);
|
|
1662
|
-
}
|
|
1663
|
-
});
|
|
1664
|
-
child.stderr.on("data", (chunk) => {
|
|
1665
|
-
if (worker === currentWorker) {
|
|
1666
|
-
currentWorker.stderr = `${currentWorker.stderr}${chunk}`.slice(-4096);
|
|
1379
|
+
catch (error) {
|
|
1380
|
+
if (error.code !== "EPERM") {
|
|
1381
|
+
throw error;
|
|
1667
1382
|
}
|
|
1668
|
-
}
|
|
1669
|
-
child.once("error", (error) => {
|
|
1670
|
-
const mapped = isSpawnUnavailable(error)
|
|
1671
|
-
? new errors_FsSafeError("helper-unavailable", "Python helper is unavailable", { cause: error })
|
|
1672
|
-
: error instanceof Error
|
|
1673
|
-
? error
|
|
1674
|
-
: new Error(String(error));
|
|
1675
|
-
rejectPending(mapped, currentWorker);
|
|
1676
|
-
});
|
|
1677
|
-
child.once("close", (code, signal) => {
|
|
1678
|
-
const stderr = currentWorker.stderr.trim();
|
|
1679
|
-
rejectPending(new errors_FsSafeError("helper-failed", stderr || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`), currentWorker);
|
|
1680
|
-
});
|
|
1681
|
-
process.once("exit", () => {
|
|
1682
|
-
child.kill("SIGTERM");
|
|
1683
|
-
});
|
|
1684
|
-
setWorkerRef(currentWorker, false);
|
|
1685
|
-
return currentWorker;
|
|
1383
|
+
}
|
|
1686
1384
|
}
|
|
1687
|
-
function
|
|
1688
|
-
|
|
1689
|
-
|
|
1385
|
+
function writeNativeFd(fd, data) {
|
|
1386
|
+
writeAll(fd, data);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/native-pinned-write.js
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
function assertWithinMaxBytes(bytes, maxBytes) {
|
|
1397
|
+
if (maxBytes !== undefined && bytes > maxBytes) {
|
|
1398
|
+
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
1690
1399
|
}
|
|
1691
|
-
const method = ref ? "ref" : "unref";
|
|
1692
|
-
const refable = value;
|
|
1693
|
-
refable[method]?.();
|
|
1694
|
-
}
|
|
1695
|
-
function setWorkerRef(currentWorker, ref) {
|
|
1696
|
-
setRefable(currentWorker.child, ref);
|
|
1697
|
-
setRefable(currentWorker.child.stdin, ref);
|
|
1698
|
-
setRefable(currentWorker.child.stdout, ref);
|
|
1699
|
-
setRefable(currentWorker.child.stderr, ref);
|
|
1700
|
-
}
|
|
1701
|
-
async function runPinnedPythonOperation(params) {
|
|
1702
|
-
const requestId = nextRequestId++;
|
|
1703
|
-
const currentWorker = getWorker();
|
|
1704
|
-
if (typeof currentWorker.child.stdin?.write !== "function") {
|
|
1705
|
-
throw new errors_FsSafeError("helper-unavailable", "Python helper stdin is unavailable");
|
|
1706
|
-
}
|
|
1707
|
-
setWorkerRef(currentWorker, true);
|
|
1708
|
-
return await new Promise((resolve, reject) => {
|
|
1709
|
-
currentWorker.pending.set(requestId, {
|
|
1710
|
-
reject,
|
|
1711
|
-
resolve: (value) => resolve(value),
|
|
1712
|
-
});
|
|
1713
|
-
const request = JSON.stringify({
|
|
1714
|
-
id: requestId,
|
|
1715
|
-
operation: params.operation,
|
|
1716
|
-
rootPath: params.rootPath,
|
|
1717
|
-
payload: params.payload,
|
|
1718
|
-
});
|
|
1719
|
-
currentWorker.child.stdin.write(`${request}\n`, (error) => {
|
|
1720
|
-
if (error) {
|
|
1721
|
-
currentWorker.pending.delete(requestId);
|
|
1722
|
-
if (currentWorker.pending.size === 0) {
|
|
1723
|
-
setWorkerRef(currentWorker, false);
|
|
1724
|
-
}
|
|
1725
|
-
reject(error);
|
|
1726
|
-
}
|
|
1727
|
-
});
|
|
1728
|
-
});
|
|
1729
1400
|
}
|
|
1730
|
-
function
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1401
|
+
async function inputToBuffer(input, maxBytes) {
|
|
1402
|
+
if (input.kind === "buffer") {
|
|
1403
|
+
const data = typeof input.data === "string"
|
|
1404
|
+
? Buffer.from(input.data, input.encoding ?? "utf8")
|
|
1405
|
+
: Buffer.from(input.data);
|
|
1406
|
+
assertWithinMaxBytes(data.byteLength, maxBytes);
|
|
1407
|
+
return data;
|
|
1408
|
+
}
|
|
1409
|
+
const chunks = [];
|
|
1410
|
+
let bytes = 0;
|
|
1411
|
+
for await (const chunk of input.stream) {
|
|
1412
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1413
|
+
bytes += buffer.byteLength;
|
|
1414
|
+
assertWithinMaxBytes(bytes, maxBytes);
|
|
1415
|
+
chunks.push(buffer);
|
|
1416
|
+
}
|
|
1417
|
+
return Buffer.concat(chunks, bytes);
|
|
1418
|
+
}
|
|
1419
|
+
function native_pinned_write_nativeOpenFlags(flags) {
|
|
1420
|
+
const closeOnExec = external_node_fs_namespaceObject.constants.O_CLOEXEC;
|
|
1421
|
+
return (flags |
|
|
1422
|
+
(closeOnExec ?? 0) |
|
|
1423
|
+
(typeof external_node_fs_namespaceObject.constants.O_NOFOLLOW === "number" ? external_node_fs_namespaceObject.constants.O_NOFOLLOW : 0));
|
|
1424
|
+
}
|
|
1425
|
+
function sameNativeIdentity(left, right) {
|
|
1426
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
1427
|
+
}
|
|
1428
|
+
async function runPinnedWriteNative(binding, params) {
|
|
1429
|
+
const data = await inputToBuffer(params.input, params.maxBytes);
|
|
1430
|
+
const root = await promises_namespaceObject.open(params.rootPath, external_node_fs_namespaceObject.constants.O_RDONLY |
|
|
1431
|
+
(typeof external_node_fs_namespaceObject.constants.O_DIRECTORY === "number" ? external_node_fs_namespaceObject.constants.O_DIRECTORY : 0));
|
|
1432
|
+
let parentFd;
|
|
1433
|
+
let tempFd;
|
|
1434
|
+
let targetFd;
|
|
1435
|
+
let tempIdentity;
|
|
1436
|
+
let parentPath = params.rootPath;
|
|
1437
|
+
const tempName = `.${params.basename}.${(0,external_node_crypto_namespaceObject.randomUUID)()}.native.tmp`;
|
|
1438
|
+
let renamed = false;
|
|
1439
|
+
try {
|
|
1440
|
+
const rootIdentity = binding.fstatIdentity(root.fd);
|
|
1441
|
+
if (params.rootIdentity && !sameNativeIdentity(params.rootIdentity, rootIdentity)) {
|
|
1442
|
+
throw new errors_FsSafeError("path-mismatch", "root path changed during native write");
|
|
1443
|
+
}
|
|
1444
|
+
if (params.mkdir) {
|
|
1445
|
+
binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
|
|
1446
|
+
}
|
|
1447
|
+
const parentFlags = external_node_fs_namespaceObject.constants.O_RDONLY |
|
|
1448
|
+
(typeof external_node_fs_namespaceObject.constants.O_DIRECTORY === "number" ? external_node_fs_namespaceObject.constants.O_DIRECTORY : 0);
|
|
1449
|
+
parentFd = binding.openBeneath(root.fd, params.relativeParentPath, parentFlags).fd;
|
|
1450
|
+
parentPath = await promises_namespaceObject.realpath(params.relativeParentPath
|
|
1451
|
+
? external_node_path_namespaceObject.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
1452
|
+
: params.rootPath);
|
|
1453
|
+
const parentPathStat = await promises_namespaceObject.lstat(parentPath);
|
|
1454
|
+
const parentIdentity = binding.fstatIdentity(parentFd);
|
|
1455
|
+
if (parentPathStat.isSymbolicLink() ||
|
|
1456
|
+
!sameNativeIdentity(parentPathStat, parentIdentity)) {
|
|
1457
|
+
throw new errors_FsSafeError("path-mismatch", "native write parent changed during resolution");
|
|
1458
|
+
}
|
|
1459
|
+
try {
|
|
1460
|
+
await promises_namespaceObject.lstat(external_node_path_namespaceObject.join(parentPath, params.basename));
|
|
1461
|
+
throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
|
|
1462
|
+
}
|
|
1463
|
+
catch (error) {
|
|
1464
|
+
if (error.code !== "ENOENT") {
|
|
1465
|
+
throw error;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
tempFd = binding.openBeneath(parentFd, tempName, native_pinned_write_nativeOpenFlags(external_node_fs_namespaceObject.constants.O_WRONLY | external_node_fs_namespaceObject.constants.O_CREAT | external_node_fs_namespaceObject.constants.O_EXCL)).fd;
|
|
1469
|
+
external_node_fs_namespaceObject.fchmodSync(tempFd, params.mode || 0o600);
|
|
1470
|
+
writeNativeFd(tempFd, data);
|
|
1471
|
+
syncNativeFileBestEffort(tempFd);
|
|
1472
|
+
tempIdentity = external_node_fs_namespaceObject.fstatSync(tempFd);
|
|
1473
|
+
binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
|
|
1474
|
+
renamed = true;
|
|
1475
|
+
targetFd = binding.openBeneath(parentFd, params.basename, native_pinned_write_nativeOpenFlags(external_node_fs_namespaceObject.constants.O_RDONLY)).fd;
|
|
1476
|
+
const targetIdentity = binding.fstatIdentity(targetFd);
|
|
1477
|
+
if (!targetIdentity.isFile || !sameNativeIdentity(tempIdentity, targetIdentity)) {
|
|
1478
|
+
throw new errors_FsSafeError("path-mismatch", "native write target changed after rename");
|
|
1479
|
+
}
|
|
1480
|
+
syncNativeFileBestEffort(parentFd);
|
|
1481
|
+
return { dev: targetIdentity.dev, ino: targetIdentity.ino };
|
|
1482
|
+
}
|
|
1483
|
+
finally {
|
|
1484
|
+
if (targetFd !== undefined) {
|
|
1485
|
+
external_node_fs_namespaceObject.closeSync(targetFd);
|
|
1486
|
+
}
|
|
1487
|
+
if (tempFd !== undefined) {
|
|
1488
|
+
external_node_fs_namespaceObject.closeSync(tempFd);
|
|
1489
|
+
}
|
|
1490
|
+
if (!renamed && parentFd !== undefined) {
|
|
1491
|
+
removeNativeCreatedFileIfStillPinned({
|
|
1492
|
+
binding,
|
|
1493
|
+
parentPath,
|
|
1494
|
+
parentFd,
|
|
1495
|
+
basename: tempName,
|
|
1496
|
+
created: tempIdentity,
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
if (parentFd !== undefined) {
|
|
1500
|
+
external_node_fs_namespaceObject.closeSync(parentFd);
|
|
1501
|
+
}
|
|
1502
|
+
await root.close().catch(() => undefined);
|
|
1734
1503
|
}
|
|
1735
1504
|
}
|
|
1505
|
+
|
|
1506
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-operation.js
|
|
1507
|
+
|
|
1736
1508
|
function validatePinnedOperationPayload(payload) {
|
|
1737
1509
|
if (typeof payload.relativePath === "string") {
|
|
1738
1510
|
validatePinnedRelativePath(payload.relativePath);
|
|
@@ -1747,9 +1519,6 @@ function validatePinnedOperationPayload(payload) {
|
|
|
1747
1519
|
validatePinnedRelativePath(payload.to);
|
|
1748
1520
|
}
|
|
1749
1521
|
}
|
|
1750
|
-
function isPinnedHelperUnavailable(error) {
|
|
1751
|
-
return error instanceof Error && "code" in error && error.code === "helper-unavailable";
|
|
1752
|
-
}
|
|
1753
1522
|
function validatePinnedRelativePath(relativePath) {
|
|
1754
1523
|
if (relativePath.length === 0 || relativePath === ".") {
|
|
1755
1524
|
return;
|
|
@@ -1771,47 +1540,91 @@ function validatePinnedRelativePath(relativePath) {
|
|
|
1771
1540
|
}
|
|
1772
1541
|
}
|
|
1773
1542
|
|
|
1774
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/
|
|
1543
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
1775
1544
|
|
|
1776
1545
|
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
}
|
|
1785
|
-
async function helperStat(rootDir, relativePath) {
|
|
1786
|
-
return await runPinnedHelper("stat", rootDir, { relativePath });
|
|
1546
|
+
const READ_CHUNK_BYTES = 64 * 1024;
|
|
1547
|
+
function assertMaxBytes(maxBytes) {
|
|
1548
|
+
if (maxBytes === Number.POSITIVE_INFINITY) {
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
1552
|
+
throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
|
|
1553
|
+
}
|
|
1787
1554
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1555
|
+
function createScratchBuffer(maxBytes) {
|
|
1556
|
+
const initialReadBytes = Number.isFinite(maxBytes)
|
|
1557
|
+
? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
|
|
1558
|
+
: READ_CHUNK_BYTES;
|
|
1559
|
+
return Buffer.allocUnsafe(Math.max(1, initialReadBytes));
|
|
1793
1560
|
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
function isPinnedPathHelperSpawnError(error) {
|
|
1800
|
-
return canFallbackFromPythonError(error);
|
|
1561
|
+
function nextReadLength(total, maxBytes, capacity) {
|
|
1562
|
+
return Number.isFinite(maxBytes)
|
|
1563
|
+
? Math.min(capacity, maxBytes - total + 1)
|
|
1564
|
+
: capacity;
|
|
1801
1565
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
});
|
|
1566
|
+
function appendChunk(params) {
|
|
1567
|
+
const total = params.total + params.bytesRead;
|
|
1568
|
+
if (total > params.maxBytes) {
|
|
1569
|
+
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got at least ${total})`);
|
|
1807
1570
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1571
|
+
params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
|
|
1572
|
+
return total;
|
|
1573
|
+
}
|
|
1574
|
+
async function readBoundedAsync(maxBytes, readChunk) {
|
|
1575
|
+
assertMaxBytes(maxBytes);
|
|
1576
|
+
const chunks = [];
|
|
1577
|
+
const scratch = createScratchBuffer(maxBytes);
|
|
1578
|
+
let total = 0;
|
|
1579
|
+
while (true) {
|
|
1580
|
+
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
1581
|
+
const bytesRead = await readChunk(scratch, length);
|
|
1582
|
+
if (bytesRead === 0) {
|
|
1583
|
+
return Buffer.concat(chunks, total);
|
|
1811
1584
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1585
|
+
total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Reads from the handle's current offset without closing it. A bounded read
|
|
1590
|
+
* consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
|
|
1591
|
+
* force an unbounded allocation.
|
|
1592
|
+
*/
|
|
1593
|
+
async function readFileHandleBounded(handle, maxBytes) {
|
|
1594
|
+
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
1595
|
+
return (await handle.read(scratch, 0, length, null)).bytesRead;
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
function readDescriptorChunk(fd, scratch, length) {
|
|
1599
|
+
return new Promise((resolve, reject) => {
|
|
1600
|
+
fs.read(fd, scratch, 0, length, null, (error, bytesRead) => {
|
|
1601
|
+
if (error) {
|
|
1602
|
+
reject(error);
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
resolve(bytesRead);
|
|
1814
1606
|
});
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
/** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
1610
|
+
async function readFileDescriptorBounded(fd, maxBytes) {
|
|
1611
|
+
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
1612
|
+
return await readDescriptorChunk(fd, scratch, length);
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
/** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
1616
|
+
function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
1617
|
+
assertMaxBytes(maxBytes);
|
|
1618
|
+
const chunks = [];
|
|
1619
|
+
const scratch = createScratchBuffer(maxBytes);
|
|
1620
|
+
let total = 0;
|
|
1621
|
+
while (true) {
|
|
1622
|
+
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
1623
|
+
const bytesRead = fs.readSync(fd, scratch, 0, length, null);
|
|
1624
|
+
if (bytesRead === 0) {
|
|
1625
|
+
return Buffer.concat(chunks, total);
|
|
1626
|
+
}
|
|
1627
|
+
total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
|
|
1815
1628
|
}
|
|
1816
1629
|
}
|
|
1817
1630
|
|
|
@@ -1819,6 +1632,11 @@ async function runPinnedPathHelper(params) {
|
|
|
1819
1632
|
|
|
1820
1633
|
|
|
1821
1634
|
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
|
|
1638
|
+
|
|
1639
|
+
const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
|
|
1822
1640
|
const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
|
|
1823
1641
|
const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
|
|
1824
1642
|
const SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX = "\t".repeat(8);
|
|
@@ -1842,28 +1660,98 @@ function serializeSidecarLockPayload(payload) {
|
|
|
1842
1660
|
ownershipToken,
|
|
1843
1661
|
};
|
|
1844
1662
|
}
|
|
1845
|
-
|
|
1663
|
+
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
1664
|
+
const resolved = external_node_path_namespaceObject.resolve(lockPath);
|
|
1665
|
+
const lexicalRelative = external_node_path_namespaceObject.relative(lockRoot.rootDir, resolved);
|
|
1666
|
+
const relative = lexicalRelative !== ".." &&
|
|
1667
|
+
!lexicalRelative.startsWith(`..${external_node_path_namespaceObject.sep}`) &&
|
|
1668
|
+
!external_node_path_namespaceObject.isAbsolute(lexicalRelative)
|
|
1669
|
+
? lexicalRelative
|
|
1670
|
+
: external_node_path_namespaceObject.relative(lockRoot.rootReal, resolved);
|
|
1671
|
+
if (!relative || relative === ".." || relative.startsWith(`..${external_node_path_namespaceObject.sep}`) || external_node_path_namespaceObject.isAbsolute(relative)) {
|
|
1672
|
+
throw new errors_FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
1673
|
+
}
|
|
1674
|
+
return relative.split(external_node_path_namespaceObject.sep).join(external_node_path_namespaceObject.posix.sep);
|
|
1675
|
+
}
|
|
1676
|
+
function parseSidecarLockPayload(raw, parser) {
|
|
1677
|
+
if (parser) {
|
|
1678
|
+
return parser(raw);
|
|
1679
|
+
}
|
|
1846
1680
|
try {
|
|
1681
|
+
const parsed = JSON.parse(raw);
|
|
1682
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
1683
|
+
}
|
|
1684
|
+
catch {
|
|
1685
|
+
return null;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
async function readSidecarLockSnapshot(lockPath, options = {}) {
|
|
1689
|
+
try {
|
|
1690
|
+
if (options.lockRoot) {
|
|
1691
|
+
const opened = await options.lockRoot.open(relativeSidecarLockPath(options.lockRoot, lockPath));
|
|
1692
|
+
try {
|
|
1693
|
+
const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
|
|
1694
|
+
return {
|
|
1695
|
+
raw,
|
|
1696
|
+
payload: parseSidecarLockPayload(raw, options.parsePayload),
|
|
1697
|
+
stat: opened.stat,
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
finally {
|
|
1701
|
+
await opened.handle.close().catch(() => undefined);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1847
1704
|
const stat = await promises_namespaceObject.lstat(lockPath);
|
|
1848
1705
|
const raw = await promises_namespaceObject.readFile(lockPath, "utf8");
|
|
1849
|
-
|
|
1850
|
-
const parsed = JSON.parse(raw);
|
|
1851
|
-
const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
1852
|
-
? parsed
|
|
1853
|
-
: null;
|
|
1854
|
-
return { raw, payload, stat };
|
|
1855
|
-
}
|
|
1856
|
-
catch {
|
|
1857
|
-
return { raw, payload: null, stat };
|
|
1858
|
-
}
|
|
1706
|
+
return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat };
|
|
1859
1707
|
}
|
|
1860
1708
|
catch (err) {
|
|
1861
|
-
if (err.code === "ENOENT"
|
|
1709
|
+
if (err.code === "ENOENT" ||
|
|
1710
|
+
(err instanceof errors_FsSafeError && err.code === "not-found")) {
|
|
1862
1711
|
return null;
|
|
1863
1712
|
}
|
|
1864
1713
|
throw err;
|
|
1865
1714
|
}
|
|
1866
1715
|
}
|
|
1716
|
+
function readSidecarLockSnapshotSync(lockPath, parsePayload) {
|
|
1717
|
+
let fd;
|
|
1718
|
+
try {
|
|
1719
|
+
const before = fsSync.lstatSync(lockPath);
|
|
1720
|
+
if (!before.isFile() || before.isSymbolicLink())
|
|
1721
|
+
return null;
|
|
1722
|
+
const noFollow = process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
|
|
1723
|
+
? fsSync.constants.O_NOFOLLOW
|
|
1724
|
+
: 0;
|
|
1725
|
+
fd = fsSync.openSync(lockPath, fsSync.constants.O_RDONLY | noFollow);
|
|
1726
|
+
const opened = fsSync.fstatSync(fd);
|
|
1727
|
+
const raw = fsSync.readFileSync(fd, "utf8");
|
|
1728
|
+
const after = fsSync.lstatSync(lockPath);
|
|
1729
|
+
if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
|
|
1730
|
+
return null;
|
|
1731
|
+
return {
|
|
1732
|
+
raw,
|
|
1733
|
+
payload: parseSidecarLockPayload(raw, parsePayload),
|
|
1734
|
+
stat: after,
|
|
1735
|
+
ownershipToken: readSidecarLockOwnershipToken(raw),
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
catch (error) {
|
|
1739
|
+
if (error.code === "ENOENT")
|
|
1740
|
+
return null;
|
|
1741
|
+
throw error;
|
|
1742
|
+
}
|
|
1743
|
+
finally {
|
|
1744
|
+
if (fd !== undefined)
|
|
1745
|
+
fsSync.closeSync(fd);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
function removeSidecarLockIfUnchangedSync(lockPath, observed) {
|
|
1749
|
+
const current = readSidecarLockSnapshotSync(lockPath);
|
|
1750
|
+
if (!current || !sidecarLockSnapshotMatches(current, observed))
|
|
1751
|
+
return false;
|
|
1752
|
+
fsSync.rmSync(lockPath);
|
|
1753
|
+
return true;
|
|
1754
|
+
}
|
|
1867
1755
|
function sidecarLockSnapshotMatches(current, observed) {
|
|
1868
1756
|
if (observed.ownershipToken !== undefined) {
|
|
1869
1757
|
return (current.stat?.isFile() === true &&
|
|
@@ -1881,16 +1769,21 @@ function sidecarLockSnapshotMatches(current, observed) {
|
|
|
1881
1769
|
}
|
|
1882
1770
|
return observed.stat !== undefined && current.stat !== undefined;
|
|
1883
1771
|
}
|
|
1884
|
-
async function removeSidecarLockIfUnchanged(lockPath, observed) {
|
|
1885
|
-
const current = await readSidecarLockSnapshot(lockPath);
|
|
1772
|
+
async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
|
|
1773
|
+
const current = await readSidecarLockSnapshot(lockPath, options);
|
|
1886
1774
|
if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
|
|
1887
1775
|
return false;
|
|
1888
1776
|
}
|
|
1889
|
-
|
|
1777
|
+
if (options.lockRoot) {
|
|
1778
|
+
await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath)).catch(() => undefined);
|
|
1779
|
+
}
|
|
1780
|
+
else {
|
|
1781
|
+
await promises_namespaceObject.rm(lockPath, { force: true }).catch(() => undefined);
|
|
1782
|
+
}
|
|
1890
1783
|
return true;
|
|
1891
1784
|
}
|
|
1892
|
-
async function sidecarLockSnapshotStillPresent(lockPath, observed) {
|
|
1893
|
-
const current = await readSidecarLockSnapshot(lockPath);
|
|
1785
|
+
async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
|
|
1786
|
+
const current = await readSidecarLockSnapshot(lockPath, options);
|
|
1894
1787
|
return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
|
|
1895
1788
|
}
|
|
1896
1789
|
async function sidecarReclaimGuardExists(pathname) {
|
|
@@ -1926,7 +1819,8 @@ async function removeStaleSidecarLockIfAllowed(params) {
|
|
|
1926
1819
|
if (!params.shouldRemoveStaleLock || params.snapshot.raw === undefined) {
|
|
1927
1820
|
return "not-approved";
|
|
1928
1821
|
}
|
|
1929
|
-
|
|
1822
|
+
const ioOptions = { lockRoot: params.lockRoot, parsePayload: params.parsePayload };
|
|
1823
|
+
if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
|
|
1930
1824
|
return "changed";
|
|
1931
1825
|
}
|
|
1932
1826
|
if (!(await params.shouldRemoveStaleLock({
|
|
@@ -1937,11 +1831,16 @@ async function removeStaleSidecarLockIfAllowed(params) {
|
|
|
1937
1831
|
}))) {
|
|
1938
1832
|
return "not-approved";
|
|
1939
1833
|
}
|
|
1940
|
-
if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot))) {
|
|
1834
|
+
if (!(await sidecarLockSnapshotStillPresent(params.lockPath, params.snapshot, ioOptions))) {
|
|
1941
1835
|
return "changed";
|
|
1942
1836
|
}
|
|
1943
1837
|
try {
|
|
1944
|
-
|
|
1838
|
+
if (params.lockRoot) {
|
|
1839
|
+
await params.lockRoot.remove(relativeSidecarLockPath(params.lockRoot, params.lockPath));
|
|
1840
|
+
}
|
|
1841
|
+
else {
|
|
1842
|
+
await promises_namespaceObject.rm(params.lockPath);
|
|
1843
|
+
}
|
|
1945
1844
|
return "removed";
|
|
1946
1845
|
}
|
|
1947
1846
|
catch (err) {
|
|
@@ -1952,12 +1851,77 @@ async function removeStaleSidecarLockIfAllowed(params) {
|
|
|
1952
1851
|
}
|
|
1953
1852
|
}
|
|
1954
1853
|
|
|
1854
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js
|
|
1855
|
+
|
|
1856
|
+
function createSidecarLockHandle(params) {
|
|
1857
|
+
let released = false;
|
|
1858
|
+
const release = async () => {
|
|
1859
|
+
if (released)
|
|
1860
|
+
return;
|
|
1861
|
+
released = true;
|
|
1862
|
+
await params.release();
|
|
1863
|
+
};
|
|
1864
|
+
return {
|
|
1865
|
+
lockPath: params.lockPath,
|
|
1866
|
+
normalizedTargetPath: params.normalizedTargetPath,
|
|
1867
|
+
verifyStillHeld: params.verifyStillHeld,
|
|
1868
|
+
release,
|
|
1869
|
+
[Symbol.asyncDispose]: release,
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
function createHeldSidecarLockHandle(params) {
|
|
1873
|
+
return createSidecarLockHandle({
|
|
1874
|
+
lockPath: params.held.lockPath,
|
|
1875
|
+
normalizedTargetPath: params.normalizedTargetPath,
|
|
1876
|
+
verifyStillHeld: async () => await sidecarLockSnapshotStillPresent(params.held.lockPath, params.held.snapshot, {
|
|
1877
|
+
lockRoot: params.held.lockRoot,
|
|
1878
|
+
parsePayload: params.held.parsePayload,
|
|
1879
|
+
}),
|
|
1880
|
+
release: params.release,
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js
|
|
1885
|
+
|
|
1886
|
+
function computeSidecarLockDelayMs(retry, attempt) {
|
|
1887
|
+
const minTimeout = retry.minTimeout ?? 50;
|
|
1888
|
+
const maxTimeout = retry.maxTimeout ?? 1000;
|
|
1889
|
+
const factor = retry.factor ?? 1;
|
|
1890
|
+
const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
|
|
1891
|
+
const jitter = retry.randomize ? 1 + Math.random() : 1;
|
|
1892
|
+
return Math.min(maxTimeout, Math.round(base * jitter));
|
|
1893
|
+
}
|
|
1894
|
+
function sidecarLockPayloadIsStale(payload, staleMs, nowMs) {
|
|
1895
|
+
const createdAt = payload &&
|
|
1896
|
+
typeof payload === "object" &&
|
|
1897
|
+
"createdAt" in payload &&
|
|
1898
|
+
typeof payload.createdAt === "string"
|
|
1899
|
+
? payload.createdAt
|
|
1900
|
+
: "";
|
|
1901
|
+
const createdAtMs = Date.parse(createdAt);
|
|
1902
|
+
return Number.isFinite(createdAtMs) && nowMs - createdAtMs > staleMs;
|
|
1903
|
+
}
|
|
1904
|
+
async function defaultSidecarLockShouldReclaim(params) {
|
|
1905
|
+
if (sidecarLockPayloadIsStale(params.payload, params.staleMs, params.nowMs))
|
|
1906
|
+
return true;
|
|
1907
|
+
try {
|
|
1908
|
+
return params.nowMs - (await promises_namespaceObject.stat(params.lockPath)).mtimeMs > params.staleMs;
|
|
1909
|
+
}
|
|
1910
|
+
catch {
|
|
1911
|
+
return true;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1955
1915
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
|
|
1956
1916
|
|
|
1957
1917
|
|
|
1958
1918
|
|
|
1959
1919
|
|
|
1960
1920
|
|
|
1921
|
+
|
|
1922
|
+
|
|
1923
|
+
|
|
1924
|
+
|
|
1961
1925
|
const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
|
|
1962
1926
|
function getGlobalManagers() {
|
|
1963
1927
|
const globalWithState = globalThis;
|
|
@@ -1983,6 +1947,9 @@ function resolveManagerState(key) {
|
|
|
1983
1947
|
// Backfill state created by fs-safe versions that predate reclaim guards.
|
|
1984
1948
|
state.reclaimCleanupRegistered ??= false;
|
|
1985
1949
|
state.reclaimGuards ??= new Set();
|
|
1950
|
+
for (const held of state.held.values()) {
|
|
1951
|
+
held.refCount ??= 1;
|
|
1952
|
+
}
|
|
1986
1953
|
}
|
|
1987
1954
|
return state;
|
|
1988
1955
|
}
|
|
@@ -2038,28 +2005,6 @@ async function resolveNormalizedTargetPath(targetPath) {
|
|
|
2038
2005
|
return resolved;
|
|
2039
2006
|
}
|
|
2040
2007
|
}
|
|
2041
|
-
function computeDelayMs(retry, attempt) {
|
|
2042
|
-
const minTimeout = retry.minTimeout ?? 50;
|
|
2043
|
-
const maxTimeout = retry.maxTimeout ?? 1000;
|
|
2044
|
-
const factor = retry.factor ?? 1;
|
|
2045
|
-
const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
|
|
2046
|
-
const jitter = retry.randomize ? 1 + Math.random() : 1;
|
|
2047
|
-
return Math.min(maxTimeout, Math.round(base * jitter));
|
|
2048
|
-
}
|
|
2049
|
-
async function defaultShouldReclaim(params) {
|
|
2050
|
-
const createdAt = typeof params.payload?.createdAt === "string" ? params.payload.createdAt : "";
|
|
2051
|
-
const createdAtMs = Date.parse(createdAt);
|
|
2052
|
-
if (Number.isFinite(createdAtMs) && params.nowMs - createdAtMs > params.staleMs) {
|
|
2053
|
-
return true;
|
|
2054
|
-
}
|
|
2055
|
-
try {
|
|
2056
|
-
const stat = await promises_namespaceObject.stat(params.lockPath);
|
|
2057
|
-
return params.nowMs - stat.mtimeMs > params.staleMs;
|
|
2058
|
-
}
|
|
2059
|
-
catch {
|
|
2060
|
-
return true;
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
2008
|
function releaseAllReclaimGuardsSync(state) {
|
|
2064
2009
|
for (const reclaimGuardPath of state.reclaimGuards) {
|
|
2065
2010
|
try {
|
|
@@ -2075,7 +2020,7 @@ function releaseAllLocksSync(state) {
|
|
|
2075
2020
|
for (const [normalizedTargetPath, held] of state.held) {
|
|
2076
2021
|
void held.handle.close().catch(() => undefined);
|
|
2077
2022
|
try {
|
|
2078
|
-
if (snapshotMatchesSync(held.lockPath, held.snapshot)) {
|
|
2023
|
+
if (!held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
|
|
2079
2024
|
external_node_fs_namespaceObject.rmSync(held.lockPath, { force: true });
|
|
2080
2025
|
}
|
|
2081
2026
|
}
|
|
@@ -2086,17 +2031,17 @@ function releaseAllLocksSync(state) {
|
|
|
2086
2031
|
}
|
|
2087
2032
|
releaseAllReclaimGuardsSync(state);
|
|
2088
2033
|
}
|
|
2089
|
-
async function releaseHeldLock(state, normalizedTargetPath, held,
|
|
2034
|
+
async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
|
|
2090
2035
|
const current = state.held.get(normalizedTargetPath);
|
|
2091
2036
|
if (current !== held) {
|
|
2092
2037
|
return false;
|
|
2093
2038
|
}
|
|
2094
|
-
if (
|
|
2095
|
-
held.
|
|
2039
|
+
if (options.force) {
|
|
2040
|
+
held.refCount = 0;
|
|
2096
2041
|
}
|
|
2097
2042
|
else {
|
|
2098
|
-
held.
|
|
2099
|
-
if (held.
|
|
2043
|
+
held.refCount -= 1;
|
|
2044
|
+
if (held.refCount > 0) {
|
|
2100
2045
|
return false;
|
|
2101
2046
|
}
|
|
2102
2047
|
}
|
|
@@ -2105,9 +2050,16 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
|
|
|
2105
2050
|
return true;
|
|
2106
2051
|
}
|
|
2107
2052
|
state.held.delete(normalizedTargetPath);
|
|
2053
|
+
if (held.compromiseTimer) {
|
|
2054
|
+
clearInterval(held.compromiseTimer);
|
|
2055
|
+
held.compromiseTimer = undefined;
|
|
2056
|
+
}
|
|
2108
2057
|
held.releasePromise = (async () => {
|
|
2109
2058
|
await held.handle.close().catch(() => undefined);
|
|
2110
|
-
await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot
|
|
2059
|
+
await removeSidecarLockIfUnchanged(held.lockPath, held.snapshot, {
|
|
2060
|
+
lockRoot: held.lockRoot,
|
|
2061
|
+
parsePayload: held.parsePayload,
|
|
2062
|
+
});
|
|
2111
2063
|
})();
|
|
2112
2064
|
try {
|
|
2113
2065
|
await held.releasePromise;
|
|
@@ -2117,6 +2069,13 @@ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
|
|
|
2117
2069
|
held.releasePromise = undefined;
|
|
2118
2070
|
}
|
|
2119
2071
|
}
|
|
2072
|
+
function handleForHeldLock(state, normalizedTargetPath, held) {
|
|
2073
|
+
return createHeldSidecarLockHandle({
|
|
2074
|
+
normalizedTargetPath,
|
|
2075
|
+
held,
|
|
2076
|
+
release: async () => await releaseHeldLock(state, normalizedTargetPath, held),
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2120
2079
|
function createSidecarLockManager(key) {
|
|
2121
2080
|
const state = resolveManagerState(key);
|
|
2122
2081
|
function ensureExitCleanupRegistered() {
|
|
@@ -2136,15 +2095,12 @@ function createSidecarLockManager(key) {
|
|
|
2136
2095
|
const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
|
|
2137
2096
|
const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
|
|
2138
2097
|
const held = state.held.get(normalizedTargetPath);
|
|
2139
|
-
if (held &&
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
release,
|
|
2146
|
-
[Symbol.asyncDispose]: release,
|
|
2147
|
-
};
|
|
2098
|
+
if (held &&
|
|
2099
|
+
options.reentrantOwner !== undefined &&
|
|
2100
|
+
held.reentrantOwner !== undefined &&
|
|
2101
|
+
options.reentrantOwner === held.reentrantOwner) {
|
|
2102
|
+
held.refCount += 1;
|
|
2103
|
+
return handleForHeldLock(state, normalizedTargetPath, held);
|
|
2148
2104
|
}
|
|
2149
2105
|
const startedAt = Date.now();
|
|
2150
2106
|
const retry = options.retry ?? {};
|
|
@@ -2167,7 +2123,7 @@ function createSidecarLockManager(key) {
|
|
|
2167
2123
|
const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
|
|
2168
2124
|
? Number.POSITIVE_INFINITY
|
|
2169
2125
|
: Math.max(0, options.timeoutMs - elapsed);
|
|
2170
|
-
const delay = Math.min(
|
|
2126
|
+
const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
|
|
2171
2127
|
attempt += 1;
|
|
2172
2128
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
2173
2129
|
};
|
|
@@ -2179,18 +2135,36 @@ function createSidecarLockManager(key) {
|
|
|
2179
2135
|
}
|
|
2180
2136
|
let handle = null;
|
|
2181
2137
|
try {
|
|
2182
|
-
handle = await promises_namespaceObject.open(lockPath, "wx");
|
|
2183
2138
|
const payload = await options.payload();
|
|
2184
2139
|
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
2185
|
-
|
|
2140
|
+
if (options.lockRoot) {
|
|
2141
|
+
const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
|
|
2142
|
+
try {
|
|
2143
|
+
await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
|
|
2144
|
+
}
|
|
2145
|
+
catch (error) {
|
|
2146
|
+
if (error instanceof errors_FsSafeError && error.code === "already-exists") {
|
|
2147
|
+
throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
|
|
2148
|
+
}
|
|
2149
|
+
throw error;
|
|
2150
|
+
}
|
|
2151
|
+
handle = (await options.lockRoot.open(relativeLockPath)).handle;
|
|
2152
|
+
}
|
|
2153
|
+
else {
|
|
2154
|
+
handle = (await createNativeExclusiveFile(lockPath, 0o600)) ?? await promises_namespaceObject.open(lockPath, "wx");
|
|
2155
|
+
await handle.writeFile(raw, "utf8");
|
|
2156
|
+
}
|
|
2186
2157
|
const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
|
|
2187
2158
|
const createdHeld = {
|
|
2188
|
-
|
|
2159
|
+
refCount: 1,
|
|
2160
|
+
reentrantOwner: options.reentrantOwner,
|
|
2189
2161
|
handle,
|
|
2190
2162
|
lockPath,
|
|
2191
2163
|
snapshot,
|
|
2192
2164
|
acquiredAt: Date.now(),
|
|
2193
2165
|
metadata: options.metadata ?? {},
|
|
2166
|
+
lockRoot: options.lockRoot,
|
|
2167
|
+
parsePayload: options.parsePayload,
|
|
2194
2168
|
};
|
|
2195
2169
|
state.held.set(normalizedTargetPath, createdHeld);
|
|
2196
2170
|
if (ownsReclaimGuard) {
|
|
@@ -2203,13 +2177,21 @@ function createSidecarLockManager(key) {
|
|
|
2203
2177
|
throw err;
|
|
2204
2178
|
}
|
|
2205
2179
|
}
|
|
2206
|
-
const
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2180
|
+
const returnedHandle = handleForHeldLock(state, normalizedTargetPath, createdHeld);
|
|
2181
|
+
const interval = options.compromiseCheckIntervalMs;
|
|
2182
|
+
if (options.onCompromised && interval !== undefined && interval > 0) {
|
|
2183
|
+
createdHeld.compromiseTimer = setInterval(() => {
|
|
2184
|
+
void returnedHandle.verifyStillHeld().then((stillHeld) => {
|
|
2185
|
+
if (!stillHeld && createdHeld.compromiseTimer) {
|
|
2186
|
+
clearInterval(createdHeld.compromiseTimer);
|
|
2187
|
+
createdHeld.compromiseTimer = undefined;
|
|
2188
|
+
options.onCompromised?.({ lockPath, normalizedTargetPath });
|
|
2189
|
+
}
|
|
2190
|
+
});
|
|
2191
|
+
}, interval);
|
|
2192
|
+
createdHeld.compromiseTimer.unref();
|
|
2193
|
+
}
|
|
2194
|
+
return returnedHandle;
|
|
2213
2195
|
}
|
|
2214
2196
|
catch (err) {
|
|
2215
2197
|
if (handle) {
|
|
@@ -2226,11 +2208,16 @@ function createSidecarLockManager(key) {
|
|
|
2226
2208
|
}
|
|
2227
2209
|
// If payload serialization/write fails, the file may be empty or
|
|
2228
2210
|
// partial JSON, so remove while our exclusive handle is still open.
|
|
2229
|
-
|
|
2211
|
+
if (!options.lockRoot) {
|
|
2212
|
+
await promises_namespaceObject.rm(lockPath, { force: true }).catch(() => undefined);
|
|
2213
|
+
}
|
|
2230
2214
|
await handle.close().catch(() => undefined);
|
|
2231
2215
|
// Windows can refuse removing an open file; retry after close but
|
|
2232
2216
|
// only if the path still points at the file identity we created.
|
|
2233
|
-
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot
|
|
2217
|
+
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
|
|
2218
|
+
lockRoot: options.lockRoot,
|
|
2219
|
+
parsePayload: options.parsePayload,
|
|
2220
|
+
});
|
|
2234
2221
|
}
|
|
2235
2222
|
if (err.code !== "EEXIST") {
|
|
2236
2223
|
throw err;
|
|
@@ -2241,11 +2228,18 @@ function createSidecarLockManager(key) {
|
|
|
2241
2228
|
continue;
|
|
2242
2229
|
}
|
|
2243
2230
|
const nowMs = Date.now();
|
|
2244
|
-
const snapshot = await readSidecarLockSnapshot(lockPath
|
|
2231
|
+
const snapshot = await readSidecarLockSnapshot(lockPath, {
|
|
2232
|
+
lockRoot: options.lockRoot,
|
|
2233
|
+
parsePayload: options.parsePayload,
|
|
2234
|
+
});
|
|
2245
2235
|
if (!snapshot) {
|
|
2246
2236
|
continue;
|
|
2247
2237
|
}
|
|
2248
|
-
|
|
2238
|
+
if (state.held.has(normalizedTargetPath)) {
|
|
2239
|
+
await waitForRetry();
|
|
2240
|
+
continue;
|
|
2241
|
+
}
|
|
2242
|
+
const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
|
|
2249
2243
|
if (await shouldReclaim({
|
|
2250
2244
|
lockPath,
|
|
2251
2245
|
normalizedTargetPath,
|
|
@@ -2254,7 +2248,10 @@ function createSidecarLockManager(key) {
|
|
|
2254
2248
|
nowMs,
|
|
2255
2249
|
heldByThisProcess: state.held.has(normalizedTargetPath),
|
|
2256
2250
|
})) {
|
|
2257
|
-
if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot
|
|
2251
|
+
if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
|
|
2252
|
+
lockRoot: options.lockRoot,
|
|
2253
|
+
parsePayload: options.parsePayload,
|
|
2254
|
+
}))) {
|
|
2258
2255
|
continue;
|
|
2259
2256
|
}
|
|
2260
2257
|
const staleRecovery = options.staleRecovery ?? "fail-closed";
|
|
@@ -2269,6 +2266,8 @@ function createSidecarLockManager(key) {
|
|
|
2269
2266
|
normalizedTargetPath,
|
|
2270
2267
|
snapshot,
|
|
2271
2268
|
shouldRemoveStaleLock: options.shouldRemoveStaleLock,
|
|
2269
|
+
lockRoot: options.lockRoot,
|
|
2270
|
+
parsePayload: options.parsePayload,
|
|
2272
2271
|
});
|
|
2273
2272
|
if (removal === "removed" || removal === "changed") {
|
|
2274
2273
|
continue;
|
|
@@ -2356,6 +2355,7 @@ function __setFsSafeTestHooksForTest(hooks) {
|
|
|
2356
2355
|
|
|
2357
2356
|
|
|
2358
2357
|
|
|
2358
|
+
|
|
2359
2359
|
function byteLength(input, encoding) {
|
|
2360
2360
|
return typeof input === "string"
|
|
2361
2361
|
? Buffer.byteLength(input, encoding ?? "utf8")
|
|
@@ -2370,7 +2370,7 @@ function assertSafeBasename(basename) {
|
|
|
2370
2370
|
throw new errors_FsSafeError("invalid-path", "invalid target path");
|
|
2371
2371
|
}
|
|
2372
2372
|
}
|
|
2373
|
-
function
|
|
2373
|
+
function pinned_write_assertWithinMaxBytes(bytes, maxBytes) {
|
|
2374
2374
|
if (maxBytes !== undefined && bytes > maxBytes) {
|
|
2375
2375
|
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
2376
2376
|
}
|
|
@@ -2390,7 +2390,7 @@ async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
|
2390
2390
|
for await (const chunk of stream) {
|
|
2391
2391
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2392
2392
|
bytes += buffer.byteLength;
|
|
2393
|
-
|
|
2393
|
+
pinned_write_assertWithinMaxBytes(bytes, maxBytes);
|
|
2394
2394
|
let offset = 0;
|
|
2395
2395
|
while (offset < buffer.byteLength) {
|
|
2396
2396
|
const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
|
|
@@ -2401,74 +2401,21 @@ async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
|
2401
2401
|
}
|
|
2402
2402
|
}
|
|
2403
2403
|
}
|
|
2404
|
-
async function inputToBase64(input, maxBytes) {
|
|
2405
|
-
if (input.kind === "buffer") {
|
|
2406
|
-
assertWithinMaxBytes(byteLength(input.data, input.encoding), maxBytes);
|
|
2407
|
-
return (typeof input.data === "string"
|
|
2408
|
-
? Buffer.from(input.data, input.encoding ?? "utf8")
|
|
2409
|
-
: input.data).toString("base64");
|
|
2410
|
-
}
|
|
2411
|
-
const chunks = [];
|
|
2412
|
-
let bytes = 0;
|
|
2413
|
-
for await (const chunk of input.stream) {
|
|
2414
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2415
|
-
bytes += buffer.byteLength;
|
|
2416
|
-
assertWithinMaxBytes(bytes, maxBytes);
|
|
2417
|
-
chunks.push(buffer);
|
|
2418
|
-
}
|
|
2419
|
-
return Buffer.concat(chunks, bytes).toString("base64");
|
|
2420
|
-
}
|
|
2421
2404
|
async function runPinnedWriteHelper(params) {
|
|
2422
2405
|
assertSafeBasename(params.basename);
|
|
2423
2406
|
validatePinnedOperationPayload({
|
|
2424
2407
|
relativeParentPath: params.relativeParentPath,
|
|
2425
2408
|
});
|
|
2426
|
-
// The
|
|
2427
|
-
//
|
|
2428
|
-
// Node fallback, where content verification can replace that one check.
|
|
2409
|
+
// The explicit compatibility policy uses the guarded Node fallback, where
|
|
2410
|
+
// content verification can replace the strict post-rename inode check.
|
|
2429
2411
|
if (params.onRenameIdentityMismatch === "verify-content") {
|
|
2430
2412
|
return await runPinnedWriteFallback(params);
|
|
2431
2413
|
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
if (params.input.kind === "stream") {
|
|
2436
|
-
try {
|
|
2437
|
-
assertPinnedPythonOperationAvailable();
|
|
2438
|
-
}
|
|
2439
|
-
catch (error) {
|
|
2440
|
-
if (canFallbackFromPythonError(error)) {
|
|
2441
|
-
return await runPinnedWriteFallback(params);
|
|
2442
|
-
}
|
|
2443
|
-
throw error;
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
const input = params.input.kind === "stream"
|
|
2447
|
-
? { kind: "buffer", data: Buffer.from(await inputToBase64(params.input, params.maxBytes), "base64") }
|
|
2448
|
-
: params.input;
|
|
2449
|
-
const payload = {
|
|
2450
|
-
base64: await inputToBase64(input, params.maxBytes),
|
|
2451
|
-
basename: params.basename,
|
|
2452
|
-
maxBytes: params.maxBytes ?? -1,
|
|
2453
|
-
mkdir: params.mkdir,
|
|
2454
|
-
mode: params.mode || 0o600,
|
|
2455
|
-
overwrite: params.overwrite !== false,
|
|
2456
|
-
relativeParentPath: params.relativeParentPath,
|
|
2457
|
-
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
2458
|
-
};
|
|
2459
|
-
try {
|
|
2460
|
-
return await runPinnedPythonOperation({
|
|
2461
|
-
operation: "write",
|
|
2462
|
-
rootPath: params.rootPath,
|
|
2463
|
-
payload,
|
|
2464
|
-
});
|
|
2465
|
-
}
|
|
2466
|
-
catch (error) {
|
|
2467
|
-
if (canFallbackFromPythonError(error)) {
|
|
2468
|
-
return await runPinnedWriteFallback({ ...params, input });
|
|
2469
|
-
}
|
|
2470
|
-
throw error;
|
|
2414
|
+
const native = getNativeBinding();
|
|
2415
|
+
if (native && params.overwrite === false) {
|
|
2416
|
+
return await runPinnedWriteNative(native, params);
|
|
2471
2417
|
}
|
|
2418
|
+
return await runPinnedWriteFallback(params);
|
|
2472
2419
|
}
|
|
2473
2420
|
async function runPinnedWriteWithRenamePolicy(params) {
|
|
2474
2421
|
const { targetPath, renameIdentity, ...writeParams } = params;
|
|
@@ -2491,34 +2438,19 @@ async function runPinnedWriteWithRenamePolicy(params) {
|
|
|
2491
2438
|
onRenameIdentityMismatch: "verify-content",
|
|
2492
2439
|
}));
|
|
2493
2440
|
}
|
|
2494
|
-
async function runPinnedCopyHelper(params) {
|
|
2495
|
-
assertSafeBasename(params.basename);
|
|
2496
|
-
validatePinnedOperationPayload({
|
|
2497
|
-
relativeParentPath: params.relativeParentPath,
|
|
2498
|
-
});
|
|
2499
|
-
return await runPinnedPythonOperation({
|
|
2500
|
-
operation: "copy",
|
|
2501
|
-
rootPath: params.rootPath,
|
|
2502
|
-
payload: {
|
|
2503
|
-
basename: params.basename,
|
|
2504
|
-
maxBytes: params.maxBytes ?? -1,
|
|
2505
|
-
mkdir: params.mkdir,
|
|
2506
|
-
mode: params.mode || 0o600,
|
|
2507
|
-
overwrite: params.overwrite !== false,
|
|
2508
|
-
relativeParentPath: params.relativeParentPath,
|
|
2509
|
-
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
2510
|
-
sourceDev: params.sourceIdentity.dev,
|
|
2511
|
-
sourceIno: params.sourceIdentity.ino,
|
|
2512
|
-
sourcePath: params.sourcePath,
|
|
2513
|
-
},
|
|
2514
|
-
});
|
|
2515
|
-
}
|
|
2516
2441
|
async function runPinnedWriteFallback(params) {
|
|
2517
|
-
|
|
2442
|
+
let parentPath = params.relativeParentPath
|
|
2518
2443
|
? external_node_path_namespaceObject.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
2519
2444
|
: params.rootPath;
|
|
2520
2445
|
if (params.mkdir) {
|
|
2521
|
-
|
|
2446
|
+
// mkdirPathComponentsWithGuards may resolve the final component through
|
|
2447
|
+
// an in-root symlink (e.g. a skill-bank layout). Use its returned real
|
|
2448
|
+
// path for the subsequent guard and target path so we don't re-check the
|
|
2449
|
+
// original, possibly-symlinked, lexical path and reject it outright.
|
|
2450
|
+
parentPath = await mkdirPathComponentsWithGuards({
|
|
2451
|
+
rootReal: params.rootPath,
|
|
2452
|
+
targetPath: parentPath,
|
|
2453
|
+
});
|
|
2522
2454
|
}
|
|
2523
2455
|
const parentGuard = params.mkdir
|
|
2524
2456
|
? await directory_guard_createAsyncDirectoryGuard(parentPath)
|
|
@@ -2534,8 +2466,9 @@ async function runPinnedWriteFallback(params) {
|
|
|
2534
2466
|
});
|
|
2535
2467
|
let created = true;
|
|
2536
2468
|
try {
|
|
2469
|
+
await handle.chmod(params.mode);
|
|
2537
2470
|
if (params.input.kind === "buffer") {
|
|
2538
|
-
|
|
2471
|
+
pinned_write_assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
|
|
2539
2472
|
if (typeof params.input.data === "string") {
|
|
2540
2473
|
await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
|
|
2541
2474
|
}
|
|
@@ -2573,8 +2506,9 @@ async function runPinnedWriteFallback(params) {
|
|
|
2573
2506
|
let renamed = false;
|
|
2574
2507
|
try {
|
|
2575
2508
|
handle = await promises_namespaceObject.open(tempPath, tempFlags, params.mode);
|
|
2509
|
+
await handle.chmod(params.mode);
|
|
2576
2510
|
if (params.input.kind === "buffer") {
|
|
2577
|
-
|
|
2511
|
+
pinned_write_assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
|
|
2578
2512
|
if (typeof params.input.data === "string") {
|
|
2579
2513
|
await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
|
|
2580
2514
|
}
|
|
@@ -2657,12 +2591,85 @@ async function runPinnedWriteFallback(params) {
|
|
|
2657
2591
|
|
|
2658
2592
|
;// CONCATENATED MODULE: external "node:os"
|
|
2659
2593
|
const external_node_os_namespaceObject = __rspack_createRequire_require("node:os");
|
|
2594
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path-existing.js
|
|
2595
|
+
|
|
2596
|
+
|
|
2597
|
+
|
|
2598
|
+
|
|
2599
|
+
function isFilesystemRoot(candidate) {
|
|
2600
|
+
return external_node_path_namespaceObject.parse(candidate).root === candidate;
|
|
2601
|
+
}
|
|
2602
|
+
async function root_path_existing_pathExists(targetPath) {
|
|
2603
|
+
try {
|
|
2604
|
+
await promises_namespaceObject.lstat(targetPath);
|
|
2605
|
+
return true;
|
|
2606
|
+
}
|
|
2607
|
+
catch (error) {
|
|
2608
|
+
if (path_isNotFoundPathError(error)) {
|
|
2609
|
+
return false;
|
|
2610
|
+
}
|
|
2611
|
+
throw error;
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
async function root_path_existing_resolvePathViaExistingAncestor(targetPath) {
|
|
2615
|
+
const normalized = external_node_path_namespaceObject.resolve(targetPath);
|
|
2616
|
+
let cursor = normalized;
|
|
2617
|
+
const missingSuffix = [];
|
|
2618
|
+
while (!isFilesystemRoot(cursor) && !(await root_path_existing_pathExists(cursor))) {
|
|
2619
|
+
missingSuffix.unshift(external_node_path_namespaceObject.basename(cursor));
|
|
2620
|
+
const parent = external_node_path_namespaceObject.dirname(cursor);
|
|
2621
|
+
if (parent === cursor) {
|
|
2622
|
+
break;
|
|
2623
|
+
}
|
|
2624
|
+
cursor = parent;
|
|
2625
|
+
}
|
|
2626
|
+
if (!(await root_path_existing_pathExists(cursor))) {
|
|
2627
|
+
return normalized;
|
|
2628
|
+
}
|
|
2629
|
+
try {
|
|
2630
|
+
const resolvedAncestor = external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(cursor));
|
|
2631
|
+
return missingSuffix.length === 0
|
|
2632
|
+
? resolvedAncestor
|
|
2633
|
+
: external_node_path_namespaceObject.resolve(resolvedAncestor, ...missingSuffix);
|
|
2634
|
+
}
|
|
2635
|
+
catch {
|
|
2636
|
+
return normalized;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
function root_path_existing_resolvePathViaExistingAncestorSync(targetPath) {
|
|
2640
|
+
const normalized = path.resolve(targetPath);
|
|
2641
|
+
let cursor = normalized;
|
|
2642
|
+
const missingSuffix = [];
|
|
2643
|
+
while (!isFilesystemRoot(cursor) && !fs.existsSync(cursor)) {
|
|
2644
|
+
missingSuffix.unshift(path.basename(cursor));
|
|
2645
|
+
const parent = path.dirname(cursor);
|
|
2646
|
+
if (parent === cursor) {
|
|
2647
|
+
break;
|
|
2648
|
+
}
|
|
2649
|
+
cursor = parent;
|
|
2650
|
+
}
|
|
2651
|
+
if (!fs.existsSync(cursor)) {
|
|
2652
|
+
return normalized;
|
|
2653
|
+
}
|
|
2654
|
+
try {
|
|
2655
|
+
const resolvedAncestor = path.resolve(fs.realpathSync(cursor));
|
|
2656
|
+
return missingSuffix.length === 0
|
|
2657
|
+
? resolvedAncestor
|
|
2658
|
+
: path.resolve(resolvedAncestor, ...missingSuffix);
|
|
2659
|
+
}
|
|
2660
|
+
catch {
|
|
2661
|
+
return normalized;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2660
2665
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-path.js
|
|
2661
2666
|
|
|
2662
2667
|
|
|
2663
2668
|
|
|
2664
2669
|
|
|
2665
2670
|
|
|
2671
|
+
|
|
2672
|
+
|
|
2666
2673
|
const ROOT_PATH_ALIAS_POLICIES = {
|
|
2667
2674
|
strict: Object.freeze({
|
|
2668
2675
|
allowFinalSymlinkForUnlink: false,
|
|
@@ -2678,7 +2685,7 @@ async function resolveRootPath(params) {
|
|
|
2678
2685
|
const absolutePath = external_node_path_namespaceObject.resolve(params.absolutePath);
|
|
2679
2686
|
const rootCanonicalPath = params.rootCanonicalPath
|
|
2680
2687
|
? external_node_path_namespaceObject.resolve(params.rootCanonicalPath)
|
|
2681
|
-
: await
|
|
2688
|
+
: await root_path_existing_resolvePathViaExistingAncestor(rootPath);
|
|
2682
2689
|
const context = createBoundaryResolutionContext({
|
|
2683
2690
|
resolveParams: params,
|
|
2684
2691
|
rootPath,
|
|
@@ -2740,15 +2747,40 @@ function isPromiseLike(value) {
|
|
|
2740
2747
|
typeof value.then === "function");
|
|
2741
2748
|
}
|
|
2742
2749
|
function createLexicalTraversalState(params) {
|
|
2743
|
-
const
|
|
2750
|
+
const rawAbsolutePath = params.params.absolutePath;
|
|
2751
|
+
const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
|
|
2752
|
+
const relative = rawRelativePath ?? external_node_path_namespaceObject.relative(params.rootPath, params.absolutePath);
|
|
2744
2753
|
return {
|
|
2745
|
-
segments: relative
|
|
2754
|
+
segments: splitTraversalSegments(relative),
|
|
2746
2755
|
allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
|
|
2747
2756
|
canonicalCursor: params.rootCanonicalPath,
|
|
2748
2757
|
lexicalCursor: params.rootPath,
|
|
2749
2758
|
preserveFinalSymlink: false,
|
|
2750
2759
|
};
|
|
2751
2760
|
}
|
|
2761
|
+
function splitTraversalSegments(value) {
|
|
2762
|
+
return value
|
|
2763
|
+
.split(process.platform === "win32" ? /[\\/]+/ : /\/+/)
|
|
2764
|
+
.filter((segment) => Boolean(segment) && segment !== ".");
|
|
2765
|
+
}
|
|
2766
|
+
function rawPathRelativeToRoot(rootPath, candidatePath) {
|
|
2767
|
+
if (!external_node_path_namespaceObject.isAbsolute(candidatePath)) {
|
|
2768
|
+
return undefined;
|
|
2769
|
+
}
|
|
2770
|
+
const root = external_node_path_namespaceObject.resolve(rootPath);
|
|
2771
|
+
const candidate = process.platform === "win32"
|
|
2772
|
+
? candidatePath.replaceAll("/", external_node_path_namespaceObject.sep)
|
|
2773
|
+
: candidatePath;
|
|
2774
|
+
if (candidate === root) {
|
|
2775
|
+
return "";
|
|
2776
|
+
}
|
|
2777
|
+
const rootWithSep = root.endsWith(external_node_path_namespaceObject.sep) ? root : `${root}${external_node_path_namespaceObject.sep}`;
|
|
2778
|
+
const candidatePrefix = candidate.slice(0, rootWithSep.length);
|
|
2779
|
+
const prefixMatches = process.platform === "win32"
|
|
2780
|
+
? candidatePrefix.toLowerCase() === rootWithSep.toLowerCase()
|
|
2781
|
+
: candidatePrefix === rootWithSep;
|
|
2782
|
+
return prefixMatches ? candidate.slice(rootWithSep.length) : undefined;
|
|
2783
|
+
}
|
|
2752
2784
|
function assertLexicalCursorInsideBoundary(params) {
|
|
2753
2785
|
assertInsideBoundary({
|
|
2754
2786
|
boundaryLabel: params.params.boundaryLabel,
|
|
@@ -2759,13 +2791,15 @@ function assertLexicalCursorInsideBoundary(params) {
|
|
|
2759
2791
|
}
|
|
2760
2792
|
function applyMissingSuffixToCanonicalCursor(params) {
|
|
2761
2793
|
const missingSuffix = params.state.segments.slice(params.missingFromIndex);
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2794
|
+
for (const segment of missingSuffix) {
|
|
2795
|
+
advanceCanonicalCursorForSegment({
|
|
2796
|
+
state: params.state,
|
|
2797
|
+
segment,
|
|
2798
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
2799
|
+
params: params.params,
|
|
2800
|
+
absolutePath: params.absolutePath,
|
|
2801
|
+
});
|
|
2802
|
+
}
|
|
2769
2803
|
}
|
|
2770
2804
|
function advanceCanonicalCursorForSegment(params) {
|
|
2771
2805
|
params.state.canonicalCursor = external_node_path_namespaceObject.resolve(params.state.canonicalCursor, params.segment);
|
|
@@ -2881,11 +2915,20 @@ function resolveAndApplySymlinkHop(params) {
|
|
|
2881
2915
|
boundaryLabel: params.boundaryLabel,
|
|
2882
2916
|
});
|
|
2883
2917
|
}
|
|
2918
|
+
function applyParentTraversalStep(params) {
|
|
2919
|
+
params.state.lexicalCursor = external_node_path_namespaceObject.resolve(params.state.lexicalCursor, "..");
|
|
2920
|
+
advanceCanonicalCursorForSegment({
|
|
2921
|
+
state: params.state,
|
|
2922
|
+
segment: "..",
|
|
2923
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
2924
|
+
params: params.resolveParams,
|
|
2925
|
+
absolutePath: params.absolutePath,
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2884
2928
|
function* iterateLexicalTraversal(state) {
|
|
2885
2929
|
for (let idx = 0; idx < state.segments.length; idx += 1) {
|
|
2886
2930
|
const segment = state.segments[idx] ?? "";
|
|
2887
2931
|
const isLast = idx === state.segments.length - 1;
|
|
2888
|
-
state.lexicalCursor = external_node_path_namespaceObject.join(state.lexicalCursor, segment);
|
|
2889
2932
|
yield { idx, segment, isLast };
|
|
2890
2933
|
}
|
|
2891
2934
|
}
|
|
@@ -2898,6 +2941,14 @@ async function resolveRootPathLexicalAsync(params) {
|
|
|
2898
2941
|
absolutePath: params.absolutePath,
|
|
2899
2942
|
};
|
|
2900
2943
|
for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
|
|
2944
|
+
if (segment === "..") {
|
|
2945
|
+
applyParentTraversalStep({
|
|
2946
|
+
...sharedStepParams,
|
|
2947
|
+
resolveParams: params.params,
|
|
2948
|
+
});
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
state.lexicalCursor = external_node_path_namespaceObject.join(state.lexicalCursor, segment);
|
|
2901
2952
|
const stat = await readLexicalStat({
|
|
2902
2953
|
...sharedStepParams,
|
|
2903
2954
|
missingFromIndex: idx,
|
|
@@ -2925,7 +2976,7 @@ async function resolveRootPathLexicalAsync(params) {
|
|
|
2925
2976
|
resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
|
|
2926
2977
|
});
|
|
2927
2978
|
}
|
|
2928
|
-
const kind = await getPathKind(
|
|
2979
|
+
const kind = await getPathKind(state.canonicalCursor, state.preserveFinalSymlink);
|
|
2929
2980
|
return finalizeLexicalResolution({
|
|
2930
2981
|
...params,
|
|
2931
2982
|
state,
|
|
@@ -2937,6 +2988,15 @@ function resolveRootPathLexicalSync(params) {
|
|
|
2937
2988
|
for (let idx = 0; idx < state.segments.length; idx += 1) {
|
|
2938
2989
|
const segment = state.segments[idx] ?? "";
|
|
2939
2990
|
const isLast = idx === state.segments.length - 1;
|
|
2991
|
+
if (segment === "..") {
|
|
2992
|
+
applyParentTraversalStep({
|
|
2993
|
+
state,
|
|
2994
|
+
rootCanonicalPath: params.rootCanonicalPath,
|
|
2995
|
+
resolveParams: params.params,
|
|
2996
|
+
absolutePath: params.absolutePath,
|
|
2997
|
+
});
|
|
2998
|
+
continue;
|
|
2999
|
+
}
|
|
2940
3000
|
state.lexicalCursor = path.join(state.lexicalCursor, segment);
|
|
2941
3001
|
const maybeStat = readLexicalStat({
|
|
2942
3002
|
state,
|
|
@@ -2978,7 +3038,7 @@ function resolveRootPathLexicalSync(params) {
|
|
|
2978
3038
|
throw new Error("Unexpected async symlink resolution");
|
|
2979
3039
|
}
|
|
2980
3040
|
}
|
|
2981
|
-
const kind = getPathKindSync(
|
|
3041
|
+
const kind = getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
|
|
2982
3042
|
return finalizeLexicalResolution({
|
|
2983
3043
|
...params,
|
|
2984
3044
|
state,
|
|
@@ -3047,7 +3107,7 @@ async function resolveOutsideLexicalCanonicalPathAsync(params) {
|
|
|
3047
3107
|
if (path_isPathInside(params.rootPath, params.absolutePath)) {
|
|
3048
3108
|
return undefined;
|
|
3049
3109
|
}
|
|
3050
|
-
return await
|
|
3110
|
+
return await root_path_existing_resolvePathViaExistingAncestor(params.absolutePath);
|
|
3051
3111
|
}
|
|
3052
3112
|
function resolveOutsideLexicalCanonicalPathSync(params) {
|
|
3053
3113
|
if (isPathInside(params.rootPath, params.absolutePath)) {
|
|
@@ -3094,60 +3154,6 @@ function buildResolvedRootPath(params) {
|
|
|
3094
3154
|
kind: params.kind.kind,
|
|
3095
3155
|
};
|
|
3096
3156
|
}
|
|
3097
|
-
async function root_path_resolvePathViaExistingAncestor(targetPath) {
|
|
3098
|
-
const normalized = external_node_path_namespaceObject.resolve(targetPath);
|
|
3099
|
-
let cursor = normalized;
|
|
3100
|
-
const missingSuffix = [];
|
|
3101
|
-
while (!isFilesystemRoot(cursor) && !(await root_path_pathExists(cursor))) {
|
|
3102
|
-
missingSuffix.unshift(external_node_path_namespaceObject.basename(cursor));
|
|
3103
|
-
const parent = external_node_path_namespaceObject.dirname(cursor);
|
|
3104
|
-
if (parent === cursor) {
|
|
3105
|
-
break;
|
|
3106
|
-
}
|
|
3107
|
-
cursor = parent;
|
|
3108
|
-
}
|
|
3109
|
-
if (!(await root_path_pathExists(cursor))) {
|
|
3110
|
-
return normalized;
|
|
3111
|
-
}
|
|
3112
|
-
try {
|
|
3113
|
-
const resolvedAncestor = external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(cursor));
|
|
3114
|
-
if (missingSuffix.length === 0) {
|
|
3115
|
-
return resolvedAncestor;
|
|
3116
|
-
}
|
|
3117
|
-
return external_node_path_namespaceObject.resolve(resolvedAncestor, ...missingSuffix);
|
|
3118
|
-
}
|
|
3119
|
-
catch {
|
|
3120
|
-
return normalized;
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
function resolvePathViaExistingAncestorSync(targetPath) {
|
|
3124
|
-
const normalized = path.resolve(targetPath);
|
|
3125
|
-
let cursor = normalized;
|
|
3126
|
-
const missingSuffix = [];
|
|
3127
|
-
while (!isFilesystemRoot(cursor) && !fs.existsSync(cursor)) {
|
|
3128
|
-
missingSuffix.unshift(path.basename(cursor));
|
|
3129
|
-
const parent = path.dirname(cursor);
|
|
3130
|
-
if (parent === cursor) {
|
|
3131
|
-
break;
|
|
3132
|
-
}
|
|
3133
|
-
cursor = parent;
|
|
3134
|
-
}
|
|
3135
|
-
if (!fs.existsSync(cursor)) {
|
|
3136
|
-
return normalized;
|
|
3137
|
-
}
|
|
3138
|
-
try {
|
|
3139
|
-
// Keep sync behavior aligned with async (`fsp.realpath`) to avoid
|
|
3140
|
-
// platform-specific canonical alias drift (notably on Windows).
|
|
3141
|
-
const resolvedAncestor = path.resolve(fs.realpathSync(cursor));
|
|
3142
|
-
if (missingSuffix.length === 0) {
|
|
3143
|
-
return resolvedAncestor;
|
|
3144
|
-
}
|
|
3145
|
-
return path.resolve(resolvedAncestor, ...missingSuffix);
|
|
3146
|
-
}
|
|
3147
|
-
catch {
|
|
3148
|
-
return normalized;
|
|
3149
|
-
}
|
|
3150
|
-
}
|
|
3151
3157
|
async function getPathKind(absolutePath, preserveFinalSymlink) {
|
|
3152
3158
|
try {
|
|
3153
3159
|
const stat = preserveFinalSymlink
|
|
@@ -3215,21 +3221,6 @@ function shortPath(value) {
|
|
|
3215
3221
|
}
|
|
3216
3222
|
return value;
|
|
3217
3223
|
}
|
|
3218
|
-
function isFilesystemRoot(candidate) {
|
|
3219
|
-
return external_node_path_namespaceObject.parse(candidate).root === candidate;
|
|
3220
|
-
}
|
|
3221
|
-
async function root_path_pathExists(targetPath) {
|
|
3222
|
-
try {
|
|
3223
|
-
await promises_namespaceObject.lstat(targetPath);
|
|
3224
|
-
return true;
|
|
3225
|
-
}
|
|
3226
|
-
catch (error) {
|
|
3227
|
-
if (path_isNotFoundPathError(error)) {
|
|
3228
|
-
return false;
|
|
3229
|
-
}
|
|
3230
|
-
throw error;
|
|
3231
|
-
}
|
|
3232
|
-
}
|
|
3233
3224
|
async function resolveSymlinkHopPath(symlinkPath) {
|
|
3234
3225
|
try {
|
|
3235
3226
|
return external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(symlinkPath));
|
|
@@ -3240,7 +3231,7 @@ async function resolveSymlinkHopPath(symlinkPath) {
|
|
|
3240
3231
|
}
|
|
3241
3232
|
const linkTarget = await promises_namespaceObject.readlink(symlinkPath);
|
|
3242
3233
|
const linkAbsolute = external_node_path_namespaceObject.resolve(external_node_path_namespaceObject.dirname(symlinkPath), linkTarget);
|
|
3243
|
-
return
|
|
3234
|
+
return root_path_existing_resolvePathViaExistingAncestor(linkAbsolute);
|
|
3244
3235
|
}
|
|
3245
3236
|
}
|
|
3246
3237
|
function resolveSymlinkHopPathSync(symlinkPath) {
|
|
@@ -3275,7 +3266,7 @@ async function assertNoPathAliasEscape(params) {
|
|
|
3275
3266
|
return;
|
|
3276
3267
|
}
|
|
3277
3268
|
await assertNoHardlinkedFinalPath({
|
|
3278
|
-
filePath: resolved.
|
|
3269
|
+
filePath: resolved.canonicalPath,
|
|
3279
3270
|
root: resolved.rootPath,
|
|
3280
3271
|
boundaryLabel: params.boundaryLabel,
|
|
3281
3272
|
allowFinalHardlinkForUnlink: params.policy?.allowFinalHardlinkForUnlink,
|
|
@@ -3309,8 +3300,80 @@ function path_policy_shortPath(value) {
|
|
|
3309
3300
|
return value;
|
|
3310
3301
|
}
|
|
3311
3302
|
|
|
3312
|
-
;// CONCATENATED MODULE:
|
|
3313
|
-
|
|
3303
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/string-coerce.js
|
|
3304
|
+
function readStringValue(value) {
|
|
3305
|
+
return typeof value === "string" ? value : undefined;
|
|
3306
|
+
}
|
|
3307
|
+
function normalizeNullableString(value) {
|
|
3308
|
+
if (typeof value !== "string") {
|
|
3309
|
+
return null;
|
|
3310
|
+
}
|
|
3311
|
+
const trimmed = value.trim();
|
|
3312
|
+
return trimmed ? trimmed : null;
|
|
3313
|
+
}
|
|
3314
|
+
function normalizeOptionalString(value) {
|
|
3315
|
+
return normalizeNullableString(value) ?? undefined;
|
|
3316
|
+
}
|
|
3317
|
+
function normalizeStringifiedOptionalString(value) {
|
|
3318
|
+
if (typeof value === "string") {
|
|
3319
|
+
return normalizeOptionalString(value);
|
|
3320
|
+
}
|
|
3321
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
3322
|
+
return normalizeOptionalString(String(value));
|
|
3323
|
+
}
|
|
3324
|
+
return undefined;
|
|
3325
|
+
}
|
|
3326
|
+
function normalizeOptionalLowercaseString(value) {
|
|
3327
|
+
return normalizeOptionalString(value)?.toLowerCase();
|
|
3328
|
+
}
|
|
3329
|
+
function normalizeLowercaseStringOrEmpty(value) {
|
|
3330
|
+
return normalizeOptionalLowercaseString(value) ?? "";
|
|
3331
|
+
}
|
|
3332
|
+
function normalizeFastMode(raw) {
|
|
3333
|
+
if (typeof raw === "boolean") {
|
|
3334
|
+
return raw;
|
|
3335
|
+
}
|
|
3336
|
+
if (!raw) {
|
|
3337
|
+
return undefined;
|
|
3338
|
+
}
|
|
3339
|
+
const key = normalizeLowercaseStringOrEmpty(raw);
|
|
3340
|
+
if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
|
|
3341
|
+
return false;
|
|
3342
|
+
}
|
|
3343
|
+
if (["on", "true", "yes", "1", "enable", "enabled", "fast"].includes(key)) {
|
|
3344
|
+
return true;
|
|
3345
|
+
}
|
|
3346
|
+
return undefined;
|
|
3347
|
+
}
|
|
3348
|
+
function lowercasePreservingWhitespace(value) {
|
|
3349
|
+
return value.toLowerCase();
|
|
3350
|
+
}
|
|
3351
|
+
function localeLowercasePreservingWhitespace(value) {
|
|
3352
|
+
return value.toLocaleLowerCase();
|
|
3353
|
+
}
|
|
3354
|
+
function resolvePrimaryStringValue(value) {
|
|
3355
|
+
if (typeof value === "string") {
|
|
3356
|
+
return normalizeOptionalString(value);
|
|
3357
|
+
}
|
|
3358
|
+
if (!value || typeof value !== "object") {
|
|
3359
|
+
return undefined;
|
|
3360
|
+
}
|
|
3361
|
+
return normalizeOptionalString(value.primary);
|
|
3362
|
+
}
|
|
3363
|
+
function normalizeOptionalThreadValue(value) {
|
|
3364
|
+
if (typeof value === "number") {
|
|
3365
|
+
return Number.isFinite(value) ? Math.trunc(value) : undefined;
|
|
3366
|
+
}
|
|
3367
|
+
return normalizeOptionalString(value);
|
|
3368
|
+
}
|
|
3369
|
+
function normalizeOptionalStringifiedId(value) {
|
|
3370
|
+
const normalized = normalizeOptionalThreadValue(value);
|
|
3371
|
+
return normalized == null ? undefined : String(normalized);
|
|
3372
|
+
}
|
|
3373
|
+
function hasNonEmptyString(value) {
|
|
3374
|
+
return normalizeOptionalString(value) !== undefined;
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3314
3377
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/local-file-access.js
|
|
3315
3378
|
|
|
3316
3379
|
|
|
@@ -3442,6 +3505,28 @@ const WINDOWS_RESERVED_DEVICE_NAMES = new Set([
|
|
|
3442
3505
|
"LPT²",
|
|
3443
3506
|
"LPT³",
|
|
3444
3507
|
]);
|
|
3508
|
+
const WINDOWS_SEPARATOR_CHAR_CODE = 0x5c;
|
|
3509
|
+
const WINDOWS_IGNORED_SPACE_CHAR_CODE = 0x20;
|
|
3510
|
+
const WINDOWS_IGNORED_DOT_CHAR_CODE = 0x2e;
|
|
3511
|
+
function trimTrailingWindowsSeparators(value) {
|
|
3512
|
+
let end = value.length;
|
|
3513
|
+
while (end > 0 && value.charCodeAt(end - 1) === WINDOWS_SEPARATOR_CHAR_CODE) {
|
|
3514
|
+
end -= 1;
|
|
3515
|
+
}
|
|
3516
|
+
return end === value.length ? value : value.slice(0, end);
|
|
3517
|
+
}
|
|
3518
|
+
function trimTrailingWindowsIgnoredChars(value) {
|
|
3519
|
+
let end = value.length;
|
|
3520
|
+
while (end > 0) {
|
|
3521
|
+
const charCode = value.charCodeAt(end - 1);
|
|
3522
|
+
if (charCode !== WINDOWS_IGNORED_SPACE_CHAR_CODE &&
|
|
3523
|
+
charCode !== WINDOWS_IGNORED_DOT_CHAR_CODE) {
|
|
3524
|
+
break;
|
|
3525
|
+
}
|
|
3526
|
+
end -= 1;
|
|
3527
|
+
}
|
|
3528
|
+
return end === value.length ? value : value.slice(0, end);
|
|
3529
|
+
}
|
|
3445
3530
|
function candidateReadPaths(filePath) {
|
|
3446
3531
|
if (!filePath.startsWith("file://")) {
|
|
3447
3532
|
return [filePath];
|
|
@@ -3470,10 +3555,10 @@ function matchPosixDeviceReadPath(filePath, cwd) {
|
|
|
3470
3555
|
return undefined;
|
|
3471
3556
|
}
|
|
3472
3557
|
function normalizeWindowsDeviceBaseName(filePath) {
|
|
3473
|
-
const normalized = filePath.replace(/\//g, "\\")
|
|
3558
|
+
const normalized = trimTrailingWindowsSeparators(filePath.replace(/\//g, "\\"));
|
|
3474
3559
|
const lastSegment = normalized.split("\\").filter(Boolean).at(-1) ?? normalized;
|
|
3475
3560
|
const withoutStream = lastSegment.split(":")[0] ?? lastSegment;
|
|
3476
|
-
const withoutTrailingIgnoredChars = withoutStream
|
|
3561
|
+
const withoutTrailingIgnoredChars = trimTrailingWindowsIgnoredChars(withoutStream);
|
|
3477
3562
|
return (withoutTrailingIgnoredChars.split(".")[0] ?? withoutTrailingIgnoredChars).toUpperCase();
|
|
3478
3563
|
}
|
|
3479
3564
|
function matchWindowsDeviceReadPath(filePath) {
|
|
@@ -3490,109 +3575,21 @@ function matchWindowsDeviceReadPath(filePath) {
|
|
|
3490
3575
|
function matchUnsafeDeviceReadPath(filePath, options = {}) {
|
|
3491
3576
|
const platform = options.platform ?? process.platform;
|
|
3492
3577
|
for (const candidate of candidateReadPaths(filePath)) {
|
|
3493
|
-
const match = platform === "win32"
|
|
3494
|
-
? matchWindowsDeviceReadPath(candidate)
|
|
3495
|
-
: matchPosixDeviceReadPath(candidate, options.cwd);
|
|
3496
|
-
if (match) {
|
|
3497
|
-
return match;
|
|
3498
|
-
}
|
|
3499
|
-
}
|
|
3500
|
-
return undefined;
|
|
3501
|
-
}
|
|
3502
|
-
function isUnsafeDeviceReadPath(filePath, options) {
|
|
3503
|
-
return matchUnsafeDeviceReadPath(filePath, options) !== undefined;
|
|
3504
|
-
}
|
|
3505
|
-
function assertNoUnsafeDeviceReadPath(filePath, options) {
|
|
3506
|
-
if (matchUnsafeDeviceReadPath(filePath, options)) {
|
|
3507
|
-
throw new errors_FsSafeError("device-path", `file reads from unsafe device paths are not allowed: ${filePath}`);
|
|
3508
|
-
}
|
|
3509
|
-
}
|
|
3510
|
-
|
|
3511
|
-
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
const READ_CHUNK_BYTES = 64 * 1024;
|
|
3515
|
-
function assertMaxBytes(maxBytes) {
|
|
3516
|
-
if (maxBytes === Number.POSITIVE_INFINITY) {
|
|
3517
|
-
return;
|
|
3518
|
-
}
|
|
3519
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
3520
|
-
throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
|
|
3521
|
-
}
|
|
3522
|
-
}
|
|
3523
|
-
function createScratchBuffer(maxBytes) {
|
|
3524
|
-
const initialReadBytes = Number.isFinite(maxBytes)
|
|
3525
|
-
? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
|
|
3526
|
-
: READ_CHUNK_BYTES;
|
|
3527
|
-
return Buffer.allocUnsafe(Math.max(1, initialReadBytes));
|
|
3528
|
-
}
|
|
3529
|
-
function nextReadLength(total, maxBytes, capacity) {
|
|
3530
|
-
return Number.isFinite(maxBytes)
|
|
3531
|
-
? Math.min(capacity, maxBytes - total + 1)
|
|
3532
|
-
: capacity;
|
|
3533
|
-
}
|
|
3534
|
-
function appendChunk(params) {
|
|
3535
|
-
const total = params.total + params.bytesRead;
|
|
3536
|
-
if (total > params.maxBytes) {
|
|
3537
|
-
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got at least ${total})`);
|
|
3538
|
-
}
|
|
3539
|
-
params.chunks.push(Buffer.from(params.scratch.subarray(0, params.bytesRead)));
|
|
3540
|
-
return total;
|
|
3541
|
-
}
|
|
3542
|
-
async function readBoundedAsync(maxBytes, readChunk) {
|
|
3543
|
-
assertMaxBytes(maxBytes);
|
|
3544
|
-
const chunks = [];
|
|
3545
|
-
const scratch = createScratchBuffer(maxBytes);
|
|
3546
|
-
let total = 0;
|
|
3547
|
-
while (true) {
|
|
3548
|
-
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
3549
|
-
const bytesRead = await readChunk(scratch, length);
|
|
3550
|
-
if (bytesRead === 0) {
|
|
3551
|
-
return Buffer.concat(chunks, total);
|
|
3552
|
-
}
|
|
3553
|
-
total = appendChunk({ chunks, scratch, bytesRead, total, maxBytes });
|
|
3554
|
-
}
|
|
3555
|
-
}
|
|
3556
|
-
/**
|
|
3557
|
-
* Reads from the handle's current offset without closing it. A bounded read
|
|
3558
|
-
* consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
|
|
3559
|
-
* force an unbounded allocation.
|
|
3560
|
-
*/
|
|
3561
|
-
async function readFileHandleBounded(handle, maxBytes) {
|
|
3562
|
-
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
3563
|
-
return (await handle.read(scratch, 0, length, null)).bytesRead;
|
|
3564
|
-
});
|
|
3565
|
-
}
|
|
3566
|
-
function readDescriptorChunk(fd, scratch, length) {
|
|
3567
|
-
return new Promise((resolve, reject) => {
|
|
3568
|
-
fs.read(fd, scratch, 0, length, null, (error, bytesRead) => {
|
|
3569
|
-
if (error) {
|
|
3570
|
-
reject(error);
|
|
3571
|
-
return;
|
|
3572
|
-
}
|
|
3573
|
-
resolve(bytesRead);
|
|
3574
|
-
});
|
|
3575
|
-
});
|
|
3576
|
-
}
|
|
3577
|
-
/** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
3578
|
-
async function readFileDescriptorBounded(fd, maxBytes) {
|
|
3579
|
-
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
3580
|
-
return await readDescriptorChunk(fd, scratch, length);
|
|
3581
|
-
});
|
|
3582
|
-
}
|
|
3583
|
-
/** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
3584
|
-
function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
3585
|
-
assertMaxBytes(maxBytes);
|
|
3586
|
-
const chunks = [];
|
|
3587
|
-
const scratch = createScratchBuffer(maxBytes);
|
|
3588
|
-
let total = 0;
|
|
3589
|
-
while (true) {
|
|
3590
|
-
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
3591
|
-
const bytesRead = fs.readSync(fd, scratch, 0, length, null);
|
|
3592
|
-
if (bytesRead === 0) {
|
|
3593
|
-
return Buffer.concat(chunks, total);
|
|
3578
|
+
const match = platform === "win32"
|
|
3579
|
+
? matchWindowsDeviceReadPath(candidate)
|
|
3580
|
+
: matchPosixDeviceReadPath(candidate, options.cwd);
|
|
3581
|
+
if (match) {
|
|
3582
|
+
return match;
|
|
3594
3583
|
}
|
|
3595
|
-
|
|
3584
|
+
}
|
|
3585
|
+
return undefined;
|
|
3586
|
+
}
|
|
3587
|
+
function isUnsafeDeviceReadPath(filePath, options) {
|
|
3588
|
+
return matchUnsafeDeviceReadPath(filePath, options) !== undefined;
|
|
3589
|
+
}
|
|
3590
|
+
function assertNoUnsafeDeviceReadPath(filePath, options) {
|
|
3591
|
+
if (matchUnsafeDeviceReadPath(filePath, options)) {
|
|
3592
|
+
throw new errors_FsSafeError("device-path", `file reads from unsafe device paths are not allowed: ${filePath}`);
|
|
3596
3593
|
}
|
|
3597
3594
|
}
|
|
3598
3595
|
|
|
@@ -3608,6 +3605,7 @@ async function read_opened_file_readOpenedFileSafely(params) {
|
|
|
3608
3605
|
: await readFileHandleBounded(params.opened.handle, params.maxBytes);
|
|
3609
3606
|
return {
|
|
3610
3607
|
buffer,
|
|
3608
|
+
containment: params.opened.containment,
|
|
3611
3609
|
realPath: params.opened.realPath,
|
|
3612
3610
|
stat: params.opened.stat,
|
|
3613
3611
|
};
|
|
@@ -3751,6 +3749,7 @@ function resolveOsHomeRelativePath(input, opts) {
|
|
|
3751
3749
|
|
|
3752
3750
|
|
|
3753
3751
|
|
|
3752
|
+
|
|
3754
3753
|
const ensureTrailingSep = (value) => value.endsWith(external_node_path_namespaceObject.sep) ? value : value + external_node_path_namespaceObject.sep;
|
|
3755
3754
|
function assertValidRootRelativePath(relativePath) {
|
|
3756
3755
|
path_assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
|
|
@@ -3795,13 +3794,31 @@ async function resolveRootContext(rootDir) {
|
|
|
3795
3794
|
rootWithSep: ensureTrailingSep(rootReal),
|
|
3796
3795
|
};
|
|
3797
3796
|
}
|
|
3798
|
-
async function resolvePathInRoot(root, relativePath) {
|
|
3797
|
+
async function resolvePathInRoot(root, relativePath, options) {
|
|
3799
3798
|
assertValidRootRelativePath(relativePath);
|
|
3800
3799
|
const expanded = await expandRelativePathWithHome(relativePath);
|
|
3801
3800
|
const resolved = external_node_path_namespaceObject.resolve(root.rootWithSep, expanded);
|
|
3802
3801
|
if (!path_isPathInside(root.rootWithSep, resolved)) {
|
|
3803
3802
|
throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
|
|
3804
3803
|
}
|
|
3804
|
+
const rawAbsolutePath = external_node_path_namespaceObject.isAbsolute(expanded)
|
|
3805
|
+
? expanded
|
|
3806
|
+
: `${root.rootWithSep}${expanded}`;
|
|
3807
|
+
try {
|
|
3808
|
+
await resolveRootPath({
|
|
3809
|
+
absolutePath: rawAbsolutePath,
|
|
3810
|
+
rootPath: root.rootReal,
|
|
3811
|
+
rootCanonicalPath: root.rootReal,
|
|
3812
|
+
boundaryLabel: "root",
|
|
3813
|
+
policy: options?.allowFinalSymlink ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
|
|
3814
|
+
});
|
|
3815
|
+
}
|
|
3816
|
+
catch (error) {
|
|
3817
|
+
const code = options?.aliasErrorCode ?? "outside-workspace";
|
|
3818
|
+
throw new errors_FsSafeError(code, code === "path-alias" ? "path alias escape blocked" : "file is outside workspace root", {
|
|
3819
|
+
cause: error instanceof Error ? error : undefined,
|
|
3820
|
+
});
|
|
3821
|
+
}
|
|
3805
3822
|
return { rootReal: root.rootReal, rootWithSep: root.rootWithSep, resolved };
|
|
3806
3823
|
}
|
|
3807
3824
|
async function resolvePathWithinRoot(params) {
|
|
@@ -3840,14 +3857,143 @@ function stringifyJsonDocument(value, replacer, space) {
|
|
|
3840
3857
|
return text;
|
|
3841
3858
|
}
|
|
3842
3859
|
|
|
3860
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/root-walk.js
|
|
3861
|
+
|
|
3862
|
+
|
|
3863
|
+
|
|
3864
|
+
function validateBudget(name, value) {
|
|
3865
|
+
if (value === undefined)
|
|
3866
|
+
return Number.POSITIVE_INFINITY;
|
|
3867
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
3868
|
+
throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
3869
|
+
}
|
|
3870
|
+
return value;
|
|
3871
|
+
}
|
|
3872
|
+
function entryKind(entry) {
|
|
3873
|
+
if (entry.isSymbolicLink)
|
|
3874
|
+
return "symlink";
|
|
3875
|
+
if (entry.isDirectory)
|
|
3876
|
+
return "directory";
|
|
3877
|
+
if (entry.isFile)
|
|
3878
|
+
return "file";
|
|
3879
|
+
return "other";
|
|
3880
|
+
}
|
|
3881
|
+
function limitEntry(relativePath) {
|
|
3882
|
+
return { relativePath, kind: "truncated", size: 0 };
|
|
3883
|
+
}
|
|
3884
|
+
async function* walkRoot(root, relativePath, options) {
|
|
3885
|
+
const maxDepth = validateBudget("maxDepth", options.maxDepth);
|
|
3886
|
+
const maxEntries = validateBudget("maxEntries", options.maxEntries);
|
|
3887
|
+
const visitedDirectories = new Set();
|
|
3888
|
+
let examined = 0;
|
|
3889
|
+
const onLimit = (atPath) => {
|
|
3890
|
+
if ((options.limitBehavior ?? "truncate") === "throw") {
|
|
3891
|
+
throw new errors_FsSafeError("too-large", `root walk budget exceeded at ${atPath || "."}`);
|
|
3892
|
+
}
|
|
3893
|
+
return limitEntry(atPath);
|
|
3894
|
+
};
|
|
3895
|
+
async function* visit(directory, depth) {
|
|
3896
|
+
options.signal?.throwIfAborted();
|
|
3897
|
+
let entries;
|
|
3898
|
+
try {
|
|
3899
|
+
const resolvedDirectory = await resolveRootPath({
|
|
3900
|
+
absolutePath: external_node_path_namespaceObject.resolve(root.rootReal, directory),
|
|
3901
|
+
rootPath: root.rootReal,
|
|
3902
|
+
rootCanonicalPath: root.rootReal,
|
|
3903
|
+
boundaryLabel: "root walk",
|
|
3904
|
+
});
|
|
3905
|
+
if (!resolvedDirectory.exists || resolvedDirectory.kind !== "directory") {
|
|
3906
|
+
throw new errors_FsSafeError("not-file", `root walk path is not a directory: ${directory || "."}`);
|
|
3907
|
+
}
|
|
3908
|
+
if (visitedDirectories.has(resolvedDirectory.canonicalPath)) {
|
|
3909
|
+
return;
|
|
3910
|
+
}
|
|
3911
|
+
visitedDirectories.add(resolvedDirectory.canonicalPath);
|
|
3912
|
+
const listingDirectory = external_node_path_namespaceObject.relative(root.rootReal, resolvedDirectory.canonicalPath)
|
|
3913
|
+
.split(external_node_path_namespaceObject.sep)
|
|
3914
|
+
.join(external_node_path_namespaceObject.posix.sep);
|
|
3915
|
+
entries = await root.list(listingDirectory, { withFileTypes: true });
|
|
3916
|
+
}
|
|
3917
|
+
catch (error) {
|
|
3918
|
+
if ((options.onDirectoryError ?? "throw") === "throw")
|
|
3919
|
+
throw error;
|
|
3920
|
+
yield { relativePath: directory, kind: "directory-error", size: 0, error };
|
|
3921
|
+
return;
|
|
3922
|
+
}
|
|
3923
|
+
for (const entry of entries) {
|
|
3924
|
+
options.signal?.throwIfAborted();
|
|
3925
|
+
const child = directory
|
|
3926
|
+
? external_node_path_namespaceObject.posix.join(directory.split(external_node_path_namespaceObject.sep).join(external_node_path_namespaceObject.posix.sep), entry.name)
|
|
3927
|
+
: entry.name;
|
|
3928
|
+
if (examined >= maxEntries) {
|
|
3929
|
+
yield onLimit(child);
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3932
|
+
examined += 1;
|
|
3933
|
+
let kind = entryKind(entry);
|
|
3934
|
+
let size = entry.size;
|
|
3935
|
+
if (kind === "symlink") {
|
|
3936
|
+
if (options.symlinkPolicy === "skip") {
|
|
3937
|
+
continue;
|
|
3938
|
+
}
|
|
3939
|
+
const resolved = await resolveRootPath({
|
|
3940
|
+
absolutePath: external_node_path_namespaceObject.resolve(root.rootReal, child),
|
|
3941
|
+
rootPath: root.rootReal,
|
|
3942
|
+
rootCanonicalPath: root.rootReal,
|
|
3943
|
+
boundaryLabel: "root walk",
|
|
3944
|
+
});
|
|
3945
|
+
if (!resolved.exists) {
|
|
3946
|
+
continue;
|
|
3947
|
+
}
|
|
3948
|
+
kind = resolved.kind === "directory" ? "directory" : resolved.kind === "file" ? "file" : "other";
|
|
3949
|
+
size = entry.size;
|
|
3950
|
+
}
|
|
3951
|
+
const walkEntry = { relativePath: child, kind, size };
|
|
3952
|
+
const filterResult = options.entryFilter?.(walkEntry) ?? "include";
|
|
3953
|
+
if (!["include", "skip", "skip-subtree"].includes(filterResult)) {
|
|
3954
|
+
throw new TypeError(`invalid root walk entryFilter result: ${String(filterResult)}`);
|
|
3955
|
+
}
|
|
3956
|
+
if (filterResult === "include") {
|
|
3957
|
+
yield walkEntry;
|
|
3958
|
+
}
|
|
3959
|
+
if (kind !== "directory") {
|
|
3960
|
+
continue;
|
|
3961
|
+
}
|
|
3962
|
+
if (filterResult === "skip-subtree") {
|
|
3963
|
+
continue;
|
|
3964
|
+
}
|
|
3965
|
+
if (depth >= maxDepth) {
|
|
3966
|
+
yield onLimit(child);
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3969
|
+
yield* visit(child, depth + 1);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
yield* visit(relativePath, 0);
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3843
3975
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/temp-cleanup.js
|
|
3844
3976
|
|
|
3977
|
+
|
|
3845
3978
|
const tempCleanupEntries = new Map();
|
|
3846
3979
|
let cleanupRegistered = false;
|
|
3980
|
+
function pathStillMatchesReceipt(entry) {
|
|
3981
|
+
if (!entry.identity) {
|
|
3982
|
+
return false;
|
|
3983
|
+
}
|
|
3984
|
+
try {
|
|
3985
|
+
return file_identity_sameFileIdentity(external_node_fs_namespaceObject.lstatSync(entry.path), entry.identity);
|
|
3986
|
+
}
|
|
3987
|
+
catch (error) {
|
|
3988
|
+
return error.code === "ENOENT";
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3847
3991
|
function cleanupRegisteredTempPathsSync() {
|
|
3848
3992
|
for (const entry of tempCleanupEntries.values()) {
|
|
3849
3993
|
try {
|
|
3850
|
-
|
|
3994
|
+
if (pathStillMatchesReceipt(entry)) {
|
|
3995
|
+
external_node_fs_namespaceObject.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
3996
|
+
}
|
|
3851
3997
|
}
|
|
3852
3998
|
catch {
|
|
3853
3999
|
// Process-exit cleanup is best-effort.
|
|
@@ -3860,13 +4006,27 @@ function registerTempPathForExit(tempPath, options) {
|
|
|
3860
4006
|
cleanupRegistered = true;
|
|
3861
4007
|
process.once("exit", cleanupRegisteredTempPathsSync);
|
|
3862
4008
|
}
|
|
3863
|
-
|
|
4009
|
+
const entry = {
|
|
3864
4010
|
path: tempPath,
|
|
3865
4011
|
recursive: options?.recursive === true,
|
|
3866
|
-
|
|
3867
|
-
|
|
4012
|
+
identity: options?.identity,
|
|
4013
|
+
};
|
|
4014
|
+
if (!entry.identity) {
|
|
4015
|
+
try {
|
|
4016
|
+
entry.identity = external_node_fs_namespaceObject.lstatSync(tempPath);
|
|
4017
|
+
}
|
|
4018
|
+
catch {
|
|
4019
|
+
// Callers that register before creation set the identity after opening.
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
tempCleanupEntries.set(tempPath, entry);
|
|
4023
|
+
const unregister = (() => {
|
|
3868
4024
|
tempCleanupEntries.delete(tempPath);
|
|
4025
|
+
});
|
|
4026
|
+
unregister.setIdentity = (identity) => {
|
|
4027
|
+
entry.identity = identity;
|
|
3869
4028
|
};
|
|
4029
|
+
return unregister;
|
|
3870
4030
|
}
|
|
3871
4031
|
function __cleanupRegisteredTempPathsForTest() {
|
|
3872
4032
|
cleanupRegisteredTempPathsSync();
|
|
@@ -3877,7 +4037,9 @@ function __cleanupRegisteredTempPathForTest(tempPath) {
|
|
|
3877
4037
|
return;
|
|
3878
4038
|
}
|
|
3879
4039
|
try {
|
|
3880
|
-
|
|
4040
|
+
if (pathStillMatchesReceipt(entry)) {
|
|
4041
|
+
fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
4042
|
+
}
|
|
3881
4043
|
}
|
|
3882
4044
|
finally {
|
|
3883
4045
|
tempCleanupEntries.delete(tempPath);
|
|
@@ -3928,9 +4090,6 @@ async function serializePathWrite(key, run) {
|
|
|
3928
4090
|
|
|
3929
4091
|
|
|
3930
4092
|
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
4093
|
|
|
3935
4094
|
|
|
3936
4095
|
|
|
@@ -3961,6 +4120,7 @@ const DEFAULT_ROOT_MAX_BYTES = 16 * 1024 * 1024;
|
|
|
3961
4120
|
function openResult(params) {
|
|
3962
4121
|
return {
|
|
3963
4122
|
handle: params.handle,
|
|
4123
|
+
containment: "best-effort",
|
|
3964
4124
|
realPath: params.realPath,
|
|
3965
4125
|
stat: params.stat,
|
|
3966
4126
|
[Symbol.asyncDispose]: () => params.handle.close().catch(() => undefined),
|
|
@@ -4079,7 +4239,7 @@ class RootHandle {
|
|
|
4079
4239
|
};
|
|
4080
4240
|
}
|
|
4081
4241
|
async resolve(relativePath) {
|
|
4082
|
-
return (await resolvePathInRoot(this.context, relativePath)).resolved;
|
|
4242
|
+
return (await resolvePathInRoot(this.context, relativePath, { allowFinalSymlink: true })).resolved;
|
|
4083
4243
|
}
|
|
4084
4244
|
async open(relativePath, options = {}) {
|
|
4085
4245
|
return await openFileInRoot(this.context, {
|
|
@@ -4218,58 +4378,34 @@ class RootHandle {
|
|
|
4218
4378
|
}
|
|
4219
4379
|
async stat(relativePath) {
|
|
4220
4380
|
assertValidRootRelativePath(relativePath);
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
}
|
|
4224
|
-
catch (error) {
|
|
4225
|
-
if (canFallbackFromPythonError(error)) {
|
|
4226
|
-
return await statPathFallback(this.context, relativePath);
|
|
4227
|
-
}
|
|
4228
|
-
throw error;
|
|
4229
|
-
}
|
|
4381
|
+
validatePinnedOperationPayload({ relativePath });
|
|
4382
|
+
return await statPathFallback(this.context, relativePath);
|
|
4230
4383
|
}
|
|
4231
4384
|
async list(relativePath, options = {}) {
|
|
4232
4385
|
assertValidRootRelativePath(relativePath);
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
? await helperReaddir(this.rootReal, relativePath, true)
|
|
4236
|
-
: await helperReaddir(this.rootReal, relativePath, false);
|
|
4237
|
-
}
|
|
4238
|
-
catch (error) {
|
|
4239
|
-
if (canFallbackFromPythonError(error)) {
|
|
4240
|
-
return await listPathFallback(this.context, relativePath, options.withFileTypes === true);
|
|
4241
|
-
}
|
|
4242
|
-
throw error;
|
|
4243
|
-
}
|
|
4386
|
+
validatePinnedOperationPayload({ relativePath });
|
|
4387
|
+
return await listPathFallback(this.context, relativePath, options.withFileTypes === true);
|
|
4244
4388
|
}
|
|
4245
4389
|
async move(fromRelative, toRelative, options = {}) {
|
|
4246
4390
|
assertValidRootRelativePath(fromRelative);
|
|
4247
4391
|
assertValidRootRelativePath(toRelative);
|
|
4392
|
+
validatePinnedOperationPayload({ from: fromRelative, to: toRelative });
|
|
4248
4393
|
const denyMutations = mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations);
|
|
4249
4394
|
await assertMoveMutationAllowed(this.context, {
|
|
4250
4395
|
fromRelative,
|
|
4251
4396
|
toRelative,
|
|
4252
4397
|
denyMutations,
|
|
4253
4398
|
});
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
fromRelative,
|
|
4265
|
-
denyMutations,
|
|
4266
|
-
overwrite: options.overwrite ?? false,
|
|
4267
|
-
toRelative,
|
|
4268
|
-
});
|
|
4269
|
-
return;
|
|
4270
|
-
}
|
|
4271
|
-
throw error;
|
|
4272
|
-
}
|
|
4399
|
+
await movePathFallback(this.context, {
|
|
4400
|
+
fromRelative,
|
|
4401
|
+
denyMutations,
|
|
4402
|
+
overwrite: options.overwrite ?? false,
|
|
4403
|
+
toRelative,
|
|
4404
|
+
});
|
|
4405
|
+
}
|
|
4406
|
+
walk(relativePath, options) {
|
|
4407
|
+
assertValidRootRelativePath(relativePath);
|
|
4408
|
+
return walkRoot(this, relativePath, options);
|
|
4273
4409
|
}
|
|
4274
4410
|
}
|
|
4275
4411
|
function readDefaults(defaults) {
|
|
@@ -4284,7 +4420,9 @@ async function root_impl_root(rootDir, defaults = {}) {
|
|
|
4284
4420
|
return new RootHandle(await resolveRootContext(rootDir), defaults);
|
|
4285
4421
|
}
|
|
4286
4422
|
async function openFileInRoot(root, params) {
|
|
4287
|
-
const { rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath
|
|
4423
|
+
const { rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath, {
|
|
4424
|
+
allowFinalSymlink: true,
|
|
4425
|
+
});
|
|
4288
4426
|
let opened;
|
|
4289
4427
|
try {
|
|
4290
4428
|
opened = await openVerifiedLocalFile(resolved, {
|
|
@@ -4355,6 +4493,15 @@ function buildAtomicWriteTempPath(targetPath) {
|
|
|
4355
4493
|
function rootWriteQueueKey(root, relativePath) {
|
|
4356
4494
|
return `${root.rootReal}\0${relativePath}`;
|
|
4357
4495
|
}
|
|
4496
|
+
async function prepareRootWriteTarget(rootReal, targetPath) {
|
|
4497
|
+
const parentPath = await mkdirPathComponentsWithGuards({
|
|
4498
|
+
rootReal,
|
|
4499
|
+
targetPath: external_node_path_namespaceObject.dirname(targetPath),
|
|
4500
|
+
});
|
|
4501
|
+
// Continue through the guarded walk's real parent instead of re-entering
|
|
4502
|
+
// the original path through a symlinked component.
|
|
4503
|
+
return external_node_path_namespaceObject.join(parentPath, external_node_path_namespaceObject.basename(targetPath));
|
|
4504
|
+
}
|
|
4358
4505
|
async function writeTempFileForAtomicReplace(params) {
|
|
4359
4506
|
const tempHandle = await promises_namespaceObject.open(params.tempPath, OPEN_WRITE_CREATE_FLAGS, params.mode);
|
|
4360
4507
|
try {
|
|
@@ -4385,7 +4532,7 @@ async function verifyAtomicWriteResult(params) {
|
|
|
4385
4532
|
}
|
|
4386
4533
|
}
|
|
4387
4534
|
async function openWritableFileInRoot(root, params) {
|
|
4388
|
-
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath);
|
|
4535
|
+
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath, { aliasErrorCode: "path-alias" });
|
|
4389
4536
|
await assertMutationNotDenied(resolved, params.denyMutations);
|
|
4390
4537
|
try {
|
|
4391
4538
|
await assertNoPathAliasEscape({
|
|
@@ -4397,15 +4544,11 @@ async function openWritableFileInRoot(root, params) {
|
|
|
4397
4544
|
catch (err) {
|
|
4398
4545
|
throw new errors_FsSafeError("path-alias", "path alias escape blocked", { cause: err });
|
|
4399
4546
|
}
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
await
|
|
4403
|
-
await promises_namespaceObject.mkdir(external_node_path_namespaceObject.dirname(resolved), { recursive: true });
|
|
4404
|
-
});
|
|
4405
|
-
}
|
|
4406
|
-
let ioPath = resolved;
|
|
4547
|
+
let ioPath = params.mkdir === false
|
|
4548
|
+
? resolved
|
|
4549
|
+
: await prepareRootWriteTarget(rootReal, resolved);
|
|
4407
4550
|
try {
|
|
4408
|
-
const resolvedRealPath = await promises_namespaceObject.realpath(
|
|
4551
|
+
const resolvedRealPath = await promises_namespaceObject.realpath(ioPath);
|
|
4409
4552
|
if (!path_isPathInside(rootWithSep, resolvedRealPath)) {
|
|
4410
4553
|
throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
|
|
4411
4554
|
}
|
|
@@ -4490,6 +4633,7 @@ async function openWritableFileInRoot(root, params) {
|
|
|
4490
4633
|
}
|
|
4491
4634
|
return {
|
|
4492
4635
|
handle,
|
|
4636
|
+
containment: "best-effort",
|
|
4493
4637
|
createdForWrite,
|
|
4494
4638
|
realPath,
|
|
4495
4639
|
stat,
|
|
@@ -4545,44 +4689,22 @@ async function appendFileInRoot(root, params) {
|
|
|
4545
4689
|
}
|
|
4546
4690
|
}
|
|
4547
4691
|
async function removePathInRoot(root, params) {
|
|
4692
|
+
validatePinnedOperationPayload({ relativePath: params.relativePath });
|
|
4548
4693
|
const resolved = await resolvePinnedRemovePathInRoot(root, params.relativePath, params.denyMutations);
|
|
4549
|
-
if (process.platform === "win32") {
|
|
4550
|
-
await removePathFallback(resolved);
|
|
4551
|
-
return;
|
|
4552
|
-
}
|
|
4553
4694
|
try {
|
|
4554
|
-
await
|
|
4555
|
-
operation: "remove",
|
|
4556
|
-
rootPath: resolved.rootReal,
|
|
4557
|
-
relativePath: resolved.relativePosix,
|
|
4558
|
-
});
|
|
4695
|
+
await removePathFallback(resolved);
|
|
4559
4696
|
}
|
|
4560
4697
|
catch (error) {
|
|
4561
|
-
if (isPinnedPathHelperSpawnError(error)) {
|
|
4562
|
-
await removePathFallback(resolved);
|
|
4563
|
-
return;
|
|
4564
|
-
}
|
|
4565
4698
|
throw normalizePinnedPathError(error);
|
|
4566
4699
|
}
|
|
4567
4700
|
}
|
|
4568
4701
|
async function mkdirPathInRoot(root, params) {
|
|
4702
|
+
validatePinnedOperationPayload({ relativePath: params.relativePath });
|
|
4569
4703
|
const resolved = await resolvePinnedPathInRoot(root, params);
|
|
4570
|
-
if (process.platform === "win32") {
|
|
4571
|
-
await mkdirPathFallback(resolved);
|
|
4572
|
-
return;
|
|
4573
|
-
}
|
|
4574
4704
|
try {
|
|
4575
|
-
await
|
|
4576
|
-
operation: "mkdirp",
|
|
4577
|
-
rootPath: resolved.rootReal,
|
|
4578
|
-
relativePath: resolved.relativePosix,
|
|
4579
|
-
});
|
|
4705
|
+
await mkdirPathFallback(resolved);
|
|
4580
4706
|
}
|
|
4581
4707
|
catch (error) {
|
|
4582
|
-
if (isPinnedPathHelperSpawnError(error)) {
|
|
4583
|
-
await mkdirPathFallback(resolved);
|
|
4584
|
-
return;
|
|
4585
|
-
}
|
|
4586
4708
|
throw normalizePinnedPathError(error);
|
|
4587
4709
|
}
|
|
4588
4710
|
}
|
|
@@ -4648,49 +4770,25 @@ async function copyFileInRoot(root, params) {
|
|
|
4648
4770
|
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${source.stat.size})`);
|
|
4649
4771
|
}
|
|
4650
4772
|
try {
|
|
4651
|
-
if (process.platform === "win32") {
|
|
4652
|
-
await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
|
|
4653
|
-
await copyFileFallback(root, params, source);
|
|
4654
|
-
});
|
|
4655
|
-
return;
|
|
4656
|
-
}
|
|
4657
4773
|
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
|
|
4658
4774
|
await serializePathWrite(pinned.targetPath, async () => {
|
|
4659
|
-
|
|
4775
|
+
await assertCopySourceCurrent(source);
|
|
4776
|
+
const identity = await runPinnedWriteHelper({
|
|
4777
|
+
rootPath: pinned.rootReal,
|
|
4778
|
+
relativeParentPath: pinned.relativeParentPath,
|
|
4779
|
+
basename: pinned.basename,
|
|
4780
|
+
mkdir: params.mkdir !== false,
|
|
4781
|
+
mode: pinned.mode,
|
|
4782
|
+
overwrite: true,
|
|
4783
|
+
maxBytes: params.maxBytes,
|
|
4784
|
+
input: { kind: "stream", stream: source.handle.createReadStream() },
|
|
4785
|
+
});
|
|
4660
4786
|
try {
|
|
4661
|
-
|
|
4662
|
-
await copyFileFallback(root, params, source);
|
|
4663
|
-
return;
|
|
4664
|
-
}
|
|
4665
|
-
identity = await runPinnedCopyHelper({
|
|
4666
|
-
rootPath: pinned.rootReal,
|
|
4667
|
-
relativeParentPath: pinned.relativeParentPath,
|
|
4668
|
-
basename: pinned.basename,
|
|
4669
|
-
mkdir: params.mkdir !== false,
|
|
4670
|
-
mode: pinned.mode,
|
|
4671
|
-
overwrite: true,
|
|
4672
|
-
maxBytes: params.maxBytes,
|
|
4673
|
-
sourcePath: source.realPath,
|
|
4674
|
-
sourceIdentity: { dev: source.stat.dev, ino: source.stat.ino },
|
|
4675
|
-
});
|
|
4787
|
+
await assertCopySourcePathCurrent(source);
|
|
4676
4788
|
}
|
|
4677
4789
|
catch (error) {
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
return;
|
|
4681
|
-
}
|
|
4682
|
-
throw normalizePinnedWriteError(error);
|
|
4683
|
-
}
|
|
4684
|
-
try {
|
|
4685
|
-
await verifyAtomicWriteResult({
|
|
4686
|
-
root,
|
|
4687
|
-
targetPath: pinned.targetPath,
|
|
4688
|
-
expectedIdentity: identity,
|
|
4689
|
-
});
|
|
4690
|
-
}
|
|
4691
|
-
catch (err) {
|
|
4692
|
-
emitWriteBoundaryWarning(`post-copy verification failed: ${String(err)}`);
|
|
4693
|
-
throw err;
|
|
4790
|
+
await removeCopyTargetIfUnchanged(pinned.targetPath, identity).catch(() => undefined);
|
|
4791
|
+
throw error;
|
|
4694
4792
|
}
|
|
4695
4793
|
});
|
|
4696
4794
|
}
|
|
@@ -4698,8 +4796,33 @@ async function copyFileInRoot(root, params) {
|
|
|
4698
4796
|
await source.handle.close().catch(() => { });
|
|
4699
4797
|
}
|
|
4700
4798
|
}
|
|
4799
|
+
async function assertCopySourceCurrent(source) {
|
|
4800
|
+
const opened = await source.handle.stat();
|
|
4801
|
+
if (!file_identity_sameFileIdentity(opened, source.stat)) {
|
|
4802
|
+
throw new errors_FsSafeError("path-mismatch", "copy source descriptor changed");
|
|
4803
|
+
}
|
|
4804
|
+
await assertCopySourcePathCurrent(source);
|
|
4805
|
+
}
|
|
4806
|
+
async function assertCopySourcePathCurrent(source) {
|
|
4807
|
+
const current = await promises_namespaceObject.lstat(source.realPath);
|
|
4808
|
+
if (current.isSymbolicLink() || !current.isFile() || !file_identity_sameFileIdentity(current, source.stat)) {
|
|
4809
|
+
throw new errors_FsSafeError("path-mismatch", "copy source path changed");
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
async function removeCopyTargetIfUnchanged(targetPath, identity) {
|
|
4813
|
+
const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_namespaceObject.dirname(targetPath));
|
|
4814
|
+
const current = await promises_namespaceObject.lstat(targetPath);
|
|
4815
|
+
if (current.isSymbolicLink() || !file_identity_sameFileIdentity(current, identity)) {
|
|
4816
|
+
return;
|
|
4817
|
+
}
|
|
4818
|
+
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
4819
|
+
await promises_namespaceObject.rm(targetPath);
|
|
4820
|
+
});
|
|
4821
|
+
}
|
|
4701
4822
|
async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
|
|
4702
|
-
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath
|
|
4823
|
+
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath, {
|
|
4824
|
+
aliasErrorCode: "path-alias",
|
|
4825
|
+
});
|
|
4703
4826
|
await assertMutationNotDenied(resolved, denyMutations);
|
|
4704
4827
|
try {
|
|
4705
4828
|
await assertNoPathAliasEscape({
|
|
@@ -4801,8 +4924,11 @@ async function resolvePinnedRootPathInRoot(root, params) {
|
|
|
4801
4924
|
const rootReal = root.rootReal;
|
|
4802
4925
|
let resolved;
|
|
4803
4926
|
try {
|
|
4927
|
+
const expandedPath = await expandRelativePathWithHome(params.relativePath);
|
|
4804
4928
|
resolved = await resolveRootPath({
|
|
4805
|
-
absolutePath: external_node_path_namespaceObject.
|
|
4929
|
+
absolutePath: external_node_path_namespaceObject.isAbsolute(expandedPath)
|
|
4930
|
+
? expandedPath
|
|
4931
|
+
: `${ensureTrailingSep(rootReal)}${expandedPath}`,
|
|
4806
4932
|
rootPath: rootReal,
|
|
4807
4933
|
rootCanonicalPath: rootReal,
|
|
4808
4934
|
boundaryLabel: "root",
|
|
@@ -4873,35 +4999,51 @@ async function listPathFallback(root, relativePath, withFileTypes) {
|
|
|
4873
4999
|
}
|
|
4874
5000
|
}
|
|
4875
5001
|
async function assertMoveMutationAllowed(root, params) {
|
|
4876
|
-
const source = await resolvePathInRoot(root, params.fromRelative
|
|
5002
|
+
const source = await resolvePathInRoot(root, params.fromRelative, {
|
|
5003
|
+
aliasErrorCode: "path-alias",
|
|
5004
|
+
allowFinalSymlink: true,
|
|
5005
|
+
});
|
|
4877
5006
|
await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
|
|
4878
|
-
const target = await resolvePathInRoot(root, params.toRelative
|
|
5007
|
+
const target = await resolvePathInRoot(root, params.toRelative, {
|
|
5008
|
+
aliasErrorCode: "path-alias",
|
|
5009
|
+
allowFinalSymlink: true,
|
|
5010
|
+
});
|
|
4879
5011
|
await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
|
|
4880
5012
|
}
|
|
4881
5013
|
async function movePathFallback(root, params) {
|
|
4882
|
-
const source = await resolvePathInRoot(root, params.fromRelative
|
|
5014
|
+
const source = await resolvePathInRoot(root, params.fromRelative, {
|
|
5015
|
+
aliasErrorCode: "path-alias",
|
|
5016
|
+
allowFinalSymlink: true,
|
|
5017
|
+
});
|
|
4883
5018
|
await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
|
|
4884
5019
|
await resolvePinnedRootPathInRoot(root, {
|
|
4885
5020
|
relativePath: params.fromRelative,
|
|
4886
5021
|
policy: PATH_ALIAS_POLICIES.strict,
|
|
4887
5022
|
});
|
|
4888
|
-
const target = await resolvePathInRoot(root, params.toRelative
|
|
5023
|
+
const target = await resolvePathInRoot(root, params.toRelative, {
|
|
5024
|
+
aliasErrorCode: "path-alias",
|
|
5025
|
+
allowFinalSymlink: true,
|
|
5026
|
+
});
|
|
4889
5027
|
await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
|
|
4890
5028
|
await resolvePinnedRootPathInRoot(root, {
|
|
4891
5029
|
relativePath: params.toRelative,
|
|
4892
5030
|
policy: PATH_ALIAS_POLICIES.unlinkTarget,
|
|
4893
5031
|
});
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
5032
|
+
const targetStat = await promises_namespaceObject.lstat(target.resolved).catch(() => undefined);
|
|
5033
|
+
const replacesFinalSymlink = process.platform !== "win32" && params.overwrite && targetStat?.isSymbolicLink() === true;
|
|
5034
|
+
if (!replacesFinalSymlink) {
|
|
5035
|
+
try {
|
|
5036
|
+
await assertNoPathAliasEscape({
|
|
5037
|
+
absolutePath: target.resolved,
|
|
5038
|
+
rootPath: target.rootReal,
|
|
5039
|
+
boundaryLabel: "root",
|
|
5040
|
+
});
|
|
5041
|
+
}
|
|
5042
|
+
catch (error) {
|
|
5043
|
+
throw new errors_FsSafeError("path-alias", "path alias escape blocked", {
|
|
5044
|
+
cause: error instanceof Error ? error : undefined,
|
|
5045
|
+
});
|
|
5046
|
+
}
|
|
4905
5047
|
}
|
|
4906
5048
|
let sourceStat;
|
|
4907
5049
|
try {
|
|
@@ -4988,6 +5130,7 @@ async function writeFileFallback(root, params) {
|
|
|
4988
5130
|
encoding: params.encoding,
|
|
4989
5131
|
mode: mode || 0o600,
|
|
4990
5132
|
});
|
|
5133
|
+
unregisterTempPath.setIdentity(writtenStat);
|
|
4991
5134
|
const commitTempPath = tempPath;
|
|
4992
5135
|
await withAsyncDirectoryGuards([destinationGuard], async () => {
|
|
4993
5136
|
await promises_namespaceObject.rename(commitTempPath, destinationPath);
|
|
@@ -5015,7 +5158,9 @@ async function writeFileFallback(root, params) {
|
|
|
5015
5158
|
}
|
|
5016
5159
|
}
|
|
5017
5160
|
async function writeMissingFileFallback(root, params) {
|
|
5018
|
-
const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath
|
|
5161
|
+
const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath, {
|
|
5162
|
+
aliasErrorCode: "path-alias",
|
|
5163
|
+
});
|
|
5019
5164
|
await assertMutationNotDenied(resolved, params.denyMutations);
|
|
5020
5165
|
try {
|
|
5021
5166
|
await assertNoPathAliasEscape({
|
|
@@ -5027,14 +5172,14 @@ async function writeMissingFileFallback(root, params) {
|
|
|
5027
5172
|
catch (err) {
|
|
5028
5173
|
throw new errors_FsSafeError("path-alias", "path alias escape blocked", { cause: err });
|
|
5029
5174
|
}
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_namespaceObject.dirname(
|
|
5175
|
+
const targetPath = params.mkdir === false
|
|
5176
|
+
? resolved
|
|
5177
|
+
: await prepareRootWriteTarget(rootReal, resolved);
|
|
5178
|
+
const parentGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_namespaceObject.dirname(targetPath));
|
|
5034
5179
|
let created = false;
|
|
5035
5180
|
try {
|
|
5036
5181
|
const { handle, writtenStat } = await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
5037
|
-
const handle = await promises_namespaceObject.open(
|
|
5182
|
+
const handle = await promises_namespaceObject.open(targetPath, OPEN_WRITE_CREATE_FLAGS, params.mode ?? 0o600);
|
|
5038
5183
|
created = true;
|
|
5039
5184
|
try {
|
|
5040
5185
|
if (typeof params.data === "string") {
|
|
@@ -5058,7 +5203,7 @@ async function writeMissingFileFallback(root, params) {
|
|
|
5058
5203
|
await handle.close();
|
|
5059
5204
|
await verifyAtomicWriteResult({
|
|
5060
5205
|
root,
|
|
5061
|
-
targetPath
|
|
5206
|
+
targetPath,
|
|
5062
5207
|
expectedIdentity: writtenStat,
|
|
5063
5208
|
});
|
|
5064
5209
|
created = false;
|
|
@@ -5073,113 +5218,101 @@ async function writeMissingFileFallback(root, params) {
|
|
|
5073
5218
|
}
|
|
5074
5219
|
finally {
|
|
5075
5220
|
if (created) {
|
|
5076
|
-
await promises_namespaceObject.rm(
|
|
5221
|
+
await promises_namespaceObject.rm(targetPath, { force: true }).catch(() => undefined);
|
|
5077
5222
|
}
|
|
5078
5223
|
}
|
|
5079
5224
|
}
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5225
|
+
|
|
5226
|
+
;// CONCATENATED MODULE: ./src/lib/symlink-path-guard.ts
|
|
5227
|
+
/**
|
|
5228
|
+
* Path-segment symlink rejection for root-bounded FS ops.
|
|
5229
|
+
*/
|
|
5230
|
+
|
|
5231
|
+
|
|
5232
|
+
function isPathWithinRoot(rootPath, candidatePath) {
|
|
5233
|
+
if (candidatePath === rootPath) {
|
|
5234
|
+
return true;
|
|
5235
|
+
}
|
|
5236
|
+
// When root is `/` (or `C:\`), appending sep would yield `//` / `C:\\` and
|
|
5237
|
+
// incorrectly reject every in-root path. Use root as the prefix when it
|
|
5238
|
+
// already ends with the separator.
|
|
5239
|
+
const prefix = rootPath.endsWith(external_node_path_namespaceObject.sep) ? rootPath : `${rootPath}${external_node_path_namespaceObject.sep}`;
|
|
5240
|
+
return candidatePath.startsWith(prefix);
|
|
5241
|
+
}
|
|
5242
|
+
function isEnoentError(error) {
|
|
5243
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
5244
|
+
}
|
|
5245
|
+
function symlinkNotAllowedError(relativePath) {
|
|
5246
|
+
return new Error(`Symlinks are not allowed: path contains symbolic link: ${JSON.stringify(relativePath)}`, {
|
|
5247
|
+
cause: new errors_FsSafeError("symlink", "symlink not allowed")
|
|
5248
|
+
});
|
|
5249
|
+
}
|
|
5250
|
+
async function lstatIfExists(path) {
|
|
5088
5251
|
try {
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
denyMutations: params.denyMutations,
|
|
5094
|
-
truncateExisting: false,
|
|
5095
|
-
});
|
|
5096
|
-
const destinationPath = target.realPath;
|
|
5097
|
-
const mode = params.mode ?? (target.stat.mode & 0o777);
|
|
5098
|
-
await target.handle.close().catch(() => { });
|
|
5099
|
-
targetClosedByUs = true;
|
|
5100
|
-
const destinationGuard = await directory_guard_createAsyncDirectoryGuard(external_node_path_namespaceObject.dirname(destinationPath));
|
|
5101
|
-
tempPath = buildAtomicWriteTempPath(destinationPath);
|
|
5102
|
-
unregisterTempPath = registerTempPathForExit(tempPath);
|
|
5103
|
-
tempHandle = await promises_namespaceObject.open(tempPath, OPEN_WRITE_CREATE_FLAGS, mode || 0o600);
|
|
5104
|
-
const sourceStream = createBoundedReadStream(source, params.maxBytes);
|
|
5105
|
-
const targetStream = tempHandle.createWriteStream();
|
|
5106
|
-
sourceStream.once("close", () => {
|
|
5107
|
-
sourceClosedByStream = true;
|
|
5108
|
-
});
|
|
5109
|
-
targetStream.once("close", () => {
|
|
5110
|
-
tempClosedByStream = true;
|
|
5111
|
-
});
|
|
5112
|
-
await (0,external_node_stream_promises_namespaceObject.pipeline)(sourceStream, targetStream);
|
|
5113
|
-
const writtenStat = await promises_namespaceObject.stat(tempPath);
|
|
5114
|
-
if (!tempClosedByStream) {
|
|
5115
|
-
await tempHandle.close().catch(() => { });
|
|
5116
|
-
tempClosedByStream = true;
|
|
5117
|
-
}
|
|
5118
|
-
tempHandle = null;
|
|
5119
|
-
const commitTempPath = tempPath;
|
|
5120
|
-
await withAsyncDirectoryGuards([destinationGuard], async () => {
|
|
5121
|
-
await promises_namespaceObject.rename(commitTempPath, destinationPath);
|
|
5122
|
-
});
|
|
5123
|
-
tempPath = null;
|
|
5124
|
-
unregisterTempPath();
|
|
5125
|
-
unregisterTempPath = null;
|
|
5126
|
-
try {
|
|
5127
|
-
await verifyAtomicWriteResult({
|
|
5128
|
-
root,
|
|
5129
|
-
targetPath: destinationPath,
|
|
5130
|
-
expectedIdentity: writtenStat,
|
|
5131
|
-
});
|
|
5132
|
-
}
|
|
5133
|
-
catch (err) {
|
|
5134
|
-
emitWriteBoundaryWarning(`post-copy verification failed: ${String(err)}`);
|
|
5135
|
-
throw err;
|
|
5252
|
+
return await (0,promises_namespaceObject.lstat)(path);
|
|
5253
|
+
} catch (error) {
|
|
5254
|
+
if (isEnoentError(error)) {
|
|
5255
|
+
return undefined;
|
|
5136
5256
|
}
|
|
5257
|
+
throw error;
|
|
5137
5258
|
}
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
throw err;
|
|
5259
|
+
}
|
|
5260
|
+
async function rejectSymlinkSegments(rootPath, relativeFromRoot, relativePath) {
|
|
5261
|
+
if (!relativeFromRoot) {
|
|
5262
|
+
return;
|
|
5143
5263
|
}
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
if (tempHandle && !tempClosedByStream) {
|
|
5149
|
-
await tempHandle.close().catch(() => { });
|
|
5264
|
+
let currentPath = rootPath;
|
|
5265
|
+
for (const segment of relativeFromRoot.split(external_node_path_namespaceObject.sep)){
|
|
5266
|
+
if (!segment || segment === ".") {
|
|
5267
|
+
continue;
|
|
5150
5268
|
}
|
|
5151
|
-
|
|
5152
|
-
|
|
5269
|
+
currentPath = (0,external_node_path_namespaceObject.join)(currentPath, segment);
|
|
5270
|
+
const stats = await lstatIfExists(currentPath);
|
|
5271
|
+
if (stats === undefined) {
|
|
5272
|
+
return;
|
|
5153
5273
|
}
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
await target.handle.close().catch(() => { });
|
|
5274
|
+
if (stats.isSymbolicLink()) {
|
|
5275
|
+
throw symlinkNotAllowedError(relativePath);
|
|
5157
5276
|
}
|
|
5158
5277
|
}
|
|
5159
5278
|
}
|
|
5279
|
+
/**
|
|
5280
|
+
* fs-safe 0.5 exists/stat/list canonicalize through in-root symlinks; open/read
|
|
5281
|
+
* still honor symlinks:"reject". Enforce reject on every root-bounded op.
|
|
5282
|
+
*/ async function assertNoSymlinksWithinRoot(targetDir, relativePath) {
|
|
5283
|
+
if (!relativePath) {
|
|
5284
|
+
return;
|
|
5285
|
+
}
|
|
5286
|
+
const rootPath = await (0,promises_namespaceObject.realpath)(targetDir);
|
|
5287
|
+
const absolutePath = (0,external_node_path_namespaceObject.resolve)(rootPath, relativePath);
|
|
5288
|
+
if (!isPathWithinRoot(rootPath, absolutePath)) {
|
|
5289
|
+
throw new Error(`Resolved path is outside target directory: ${relativePath}`);
|
|
5290
|
+
}
|
|
5291
|
+
await rejectSymlinkSegments(rootPath, (0,external_node_path_namespaceObject.relative)(rootPath, absolutePath), relativePath);
|
|
5292
|
+
}
|
|
5160
5293
|
|
|
5161
5294
|
;// CONCATENATED MODULE: ./src/lib/safe-fs.ts
|
|
5162
5295
|
|
|
5296
|
+
|
|
5163
5297
|
function mapFsSafeError(error, relativePath) {
|
|
5164
|
-
if (error instanceof errors_FsSafeError) {
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
}
|
|
5298
|
+
if (!(error instanceof errors_FsSafeError)) {
|
|
5299
|
+
throw error;
|
|
5300
|
+
}
|
|
5301
|
+
const safePath = JSON.stringify(relativePath);
|
|
5302
|
+
if (error.code === "outside-workspace" || error.code === "invalid-path") {
|
|
5303
|
+
throw new Error(`Resolved path is outside target directory: ${safePath}`, {
|
|
5304
|
+
cause: error
|
|
5305
|
+
});
|
|
5306
|
+
}
|
|
5307
|
+
if (error.code === "path-alias" || error.code === "symlink") {
|
|
5308
|
+
throw new Error(`Symlinks are not allowed: path contains symbolic link: ${safePath}`, {
|
|
5309
|
+
cause: error
|
|
5310
|
+
});
|
|
5311
|
+
}
|
|
5312
|
+
if (error.code === "too-large") {
|
|
5313
|
+
throw new Error(`File ${safePath} exceeds size limit: ${error.message}`, {
|
|
5314
|
+
cause: error
|
|
5315
|
+
});
|
|
5183
5316
|
}
|
|
5184
5317
|
throw error;
|
|
5185
5318
|
}
|
|
@@ -5200,6 +5333,7 @@ function toSafeDirEntry(entry) {
|
|
|
5200
5333
|
}
|
|
5201
5334
|
async function readBytesWithinRoot(targetDir, relativePath, maxBytes) {
|
|
5202
5335
|
try {
|
|
5336
|
+
await assertNoSymlinksWithinRoot(targetDir, relativePath);
|
|
5203
5337
|
const safeRoot = await createSafeRoot(targetDir, maxBytes);
|
|
5204
5338
|
return await safeRoot.readBytes(relativePath, {
|
|
5205
5339
|
maxBytes
|
|
@@ -5210,6 +5344,7 @@ async function readBytesWithinRoot(targetDir, relativePath, maxBytes) {
|
|
|
5210
5344
|
}
|
|
5211
5345
|
async function readTextWithinRoot(targetDir, relativePath, maxBytes) {
|
|
5212
5346
|
try {
|
|
5347
|
+
await assertNoSymlinksWithinRoot(targetDir, relativePath);
|
|
5213
5348
|
const safeRoot = await createSafeRoot(targetDir, maxBytes);
|
|
5214
5349
|
return await safeRoot.readText(relativePath, {
|
|
5215
5350
|
maxBytes
|
|
@@ -5220,6 +5355,7 @@ async function readTextWithinRoot(targetDir, relativePath, maxBytes) {
|
|
|
5220
5355
|
}
|
|
5221
5356
|
async function listDirectoryWithinRoot(targetDir, relativePath) {
|
|
5222
5357
|
try {
|
|
5358
|
+
await assertNoSymlinksWithinRoot(targetDir, relativePath);
|
|
5223
5359
|
const safeRoot = await createSafeRoot(targetDir);
|
|
5224
5360
|
const entries = await safeRoot.list(relativePath, {
|
|
5225
5361
|
withFileTypes: true
|
|
@@ -5231,6 +5367,7 @@ async function listDirectoryWithinRoot(targetDir, relativePath) {
|
|
|
5231
5367
|
}
|
|
5232
5368
|
async function statWithinRoot(targetDir, relativePath) {
|
|
5233
5369
|
try {
|
|
5370
|
+
await assertNoSymlinksWithinRoot(targetDir, relativePath);
|
|
5234
5371
|
const safeRoot = await createSafeRoot(targetDir);
|
|
5235
5372
|
return await safeRoot.stat(relativePath);
|
|
5236
5373
|
} catch (error) {
|
|
@@ -5239,6 +5376,7 @@ async function statWithinRoot(targetDir, relativePath) {
|
|
|
5239
5376
|
}
|
|
5240
5377
|
async function pathExistsWithinRoot(targetDir, relativePath) {
|
|
5241
5378
|
try {
|
|
5379
|
+
await assertNoSymlinksWithinRoot(targetDir, relativePath);
|
|
5242
5380
|
const safeRoot = await createSafeRoot(targetDir);
|
|
5243
5381
|
return await safeRoot.exists(relativePath);
|
|
5244
5382
|
} catch (error) {
|
|
@@ -13973,8 +14111,6 @@ function hasProtoKey(value) {
|
|
|
13973
14111
|
return hasProtoKeyAtDepth(value, 0);
|
|
13974
14112
|
}
|
|
13975
14113
|
|
|
13976
|
-
;// CONCATENATED MODULE: external "node:module"
|
|
13977
|
-
const external_node_module_namespaceObject = __rspack_createRequire_require("node:module");
|
|
13978
14114
|
;// CONCATENATED MODULE: ./src/gateways/resolve-sdk.ts
|
|
13979
14115
|
|
|
13980
14116
|
|
|
@@ -16252,4 +16388,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
|
|
|
16252
16388
|
// module factories are used so entry inlining is disabled
|
|
16253
16389
|
// startup
|
|
16254
16390
|
// Load entry module and return exports
|
|
16255
|
-
var __webpack_exports__ = __webpack_require__(
|
|
16391
|
+
var __webpack_exports__ = __webpack_require__(418);
|