@openclaw/fs-safe 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/LICENSE +1 -0
- package/README.md +36 -5
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +3 -2
- package/dist/advanced.d.ts +4 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +4 -0
- package/dist/archive-durability.d.ts +6 -6
- package/dist/archive-durability.d.ts.map +1 -1
- package/dist/archive-durability.js +1 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -5
- package/dist/archive-gzip-tail.d.ts +1 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +3 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +4 -2
- package/dist/archive-merge.d.ts +5 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -12
- package/dist/archive-native.js +4 -4
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +17 -9
- package/dist/archive-staging.d.ts +6 -3
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +42 -22
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +7 -6
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +16 -13
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +48 -12
- package/dist/archive-zip-loader.d.ts +6 -0
- package/dist/archive-zip-loader.d.ts.map +1 -0
- package/dist/archive-zip-loader.js +38 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +13 -8
- package/dist/archive-zip-preflight.d.ts +2 -3
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +2 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +12 -10
- package/dist/bounded-read.d.ts +5 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +16 -9
- package/dist/copy-file-input.d.ts +0 -1
- package/dist/copy-file-input.d.ts.map +1 -1
- package/dist/copy-file-input.js +4 -26
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +57 -26
- package/dist/copy.d.ts +1 -1
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +3 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +5 -4
- package/dist/directory-guard.d.ts +11 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +53 -11
- package/dist/durability.d.ts +1 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +1 -1
- package/dist/file-handle-transfer.d.ts +14 -0
- package/dist/file-handle-transfer.d.ts.map +1 -0
- package/dist/file-handle-transfer.js +64 -0
- package/dist/file-hash.d.ts +3 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +91 -31
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -4
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +7 -5
- package/dist/file-store-path.d.ts +3 -0
- package/dist/file-store-path.d.ts.map +1 -0
- package/dist/file-store-path.js +27 -0
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +6 -4
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +56 -44
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +2 -18
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +2 -1
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +3 -2
- package/dist/guest-dispatch-python.d.ts +2 -0
- package/dist/guest-dispatch-python.d.ts.map +1 -0
- package/dist/guest-dispatch-python.js +117 -0
- package/dist/guest-native-python.d.ts +4 -0
- package/dist/guest-native-python.d.ts.map +1 -0
- package/dist/guest-native-python.js +135 -0
- package/dist/guest.d.ts +9 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +413 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +3 -2
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +6 -4
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -1
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +2 -1
- package/dist/move-path-stage.d.ts.map +1 -1
- package/dist/move-path-stage.js +2 -1
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +4 -3
- package/dist/mutation-authority.d.ts +1 -0
- package/dist/mutation-authority.d.ts.map +1 -1
- package/dist/mutation-authority.js +4 -4
- package/dist/native-binding.d.ts +7 -2
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +3 -2
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +2 -1
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +5 -4
- package/dist/output.d.ts +2 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +2 -0
- package/dist/overwrite-file-handle.d.ts +8 -0
- package/dist/overwrite-file-handle.d.ts.map +1 -0
- package/dist/overwrite-file-handle.js +42 -0
- package/dist/path-case.d.ts +7 -0
- package/dist/path-case.d.ts.map +1 -0
- package/dist/path-case.js +136 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +2 -1
- package/dist/permissions-windows.d.ts +1 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +48 -6
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +3 -1
- package/dist/pinned-write.d.ts +2 -2
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +2 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +6 -4
- package/dist/realpath.d.ts +4 -0
- package/dist/realpath.d.ts.map +1 -0
- package/dist/realpath.js +43 -0
- package/dist/recursive-mkdir-path.d.ts +3 -0
- package/dist/recursive-mkdir-path.d.ts.map +1 -0
- package/dist/recursive-mkdir-path.js +8 -0
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +2 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +23 -31
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +7 -12
- package/dist/replace-file-mode.d.ts +3 -0
- package/dist/replace-file-mode.d.ts.map +1 -0
- package/dist/replace-file-mode.js +10 -0
- package/dist/replace-file.d.ts +1 -0
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +14 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +8 -8
- package/dist/root-create-input.d.ts +10 -0
- package/dist/root-create-input.d.ts.map +1 -0
- package/dist/root-create-input.js +80 -0
- package/dist/root-directory-list.d.ts +3 -1
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +21 -3
- package/dist/root-entries.d.ts +11 -0
- package/dist/root-entries.d.ts.map +1 -0
- package/dist/root-entries.js +61 -0
- package/dist/root-errors.d.ts +5 -5
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +13 -12
- package/dist/root-impl.d.ts +13 -3
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +40 -36
- package/dist/root-options.d.ts +12 -1
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +4 -3
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +3 -2
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +13 -9
- package/dist/root-remove.d.ts +5 -0
- package/dist/root-remove.d.ts.map +1 -0
- package/dist/root-remove.js +286 -0
- package/dist/root-symlink-policy.d.ts +2 -1
- package/dist/root-symlink-policy.d.ts.map +1 -1
- package/dist/root-symlink-policy.js +2 -2
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +2 -1
- package/dist/root.d.ts +2 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +21 -4
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +2 -1
- package/dist/sibling-staged-file.d.ts +1 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +42 -8
- package/dist/sibling-temp.d.ts +2 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +6 -4
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +15 -4
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +17 -0
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +4 -3
- package/dist/temp-target.d.ts +14 -12
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +12 -6
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +7 -5
- package/dist/unicode-path.d.ts +3 -0
- package/dist/unicode-path.d.ts.map +1 -0
- package/dist/unicode-path.js +13 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +3 -2
- package/dist/write-file-handle.d.ts +1 -0
- package/dist/write-file-handle.d.ts.map +1 -1
- package/dist/write-file-handle.js +3 -2
- package/docs/advanced.md +4 -0
- package/docs/archive.md +25 -5
- package/docs/atomic.md +17 -1
- package/docs/config.md +1 -0
- package/docs/contributing.md +29 -1
- package/docs/copy.md +75 -6
- package/docs/directory-identity.md +85 -0
- package/docs/durability.md +36 -1
- package/docs/entries.md +109 -0
- package/docs/errors.md +3 -3
- package/docs/file-store.md +15 -0
- package/docs/guest.md +141 -0
- package/docs/in-place-write.md +81 -0
- package/docs/index.md +2 -0
- package/docs/install.md +31 -0
- package/docs/native-helper.md +10 -3
- package/docs/native.md +4 -0
- package/docs/output.md +32 -6
- package/docs/path-case.md +64 -0
- package/docs/path-scope.md +1 -1
- package/docs/permissions.md +11 -2
- package/docs/public-api.md +31 -2
- package/docs/root.md +30 -2
- package/docs/secure-file.md +2 -0
- package/docs/sidecar-lock.md +12 -3
- package/docs/temp.md +35 -6
- package/docs/types.md +1 -1
- package/docs/writing.md +153 -3
- package/package.json +14 -8
package/dist/file-store.js
CHANGED
|
@@ -5,35 +5,19 @@ import { normalizeMaxBytes } from "./byte-budget.js";
|
|
|
5
5
|
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { assertFileStoreMaxBytes } from "./file-store-limit.js";
|
|
8
|
+
import { assertRelativePath, resolveStorePath } from "./file-store-path.js";
|
|
8
9
|
import { pruneExpiredStoreEntries } from "./file-store-prune.js";
|
|
9
10
|
import { ensureParentInRoot, openPrivateStoreLockRoot, openWritableStoreRoot, writeStreamToTempSource, } from "./file-store-boundary.js";
|
|
10
11
|
import { writeFileSyncAtomic } from "./file-store-sync-write.js";
|
|
11
12
|
import { createJsonStore } from "./json-document-store.js";
|
|
12
13
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
13
|
-
import { isNotFoundPathError
|
|
14
|
+
import { isNotFoundPathError } from "./path.js";
|
|
14
15
|
import { throwFsSafeReadError } from "./root-errors.js";
|
|
15
16
|
import { root } from "./root.js";
|
|
16
17
|
import { DEFAULT_ROOT_MAX_BYTES } from "./root-impl.js";
|
|
17
18
|
import { readRegularFile } from "./regular-file.js";
|
|
18
19
|
import { matchRootFileOpenFailure, openRootFileSync } from "./root-file.js";
|
|
19
20
|
import { writeSecretFileAtomic } from "./secret-file.js";
|
|
20
|
-
function assertRelativePath(relativePath) {
|
|
21
|
-
const raw = relativePath.trim();
|
|
22
|
-
if (!raw || raw !== relativePath) {
|
|
23
|
-
throw new FsSafeError("invalid-path", "store key must be non-empty and unpadded");
|
|
24
|
-
}
|
|
25
|
-
const segments = splitSafeRelativePath(raw);
|
|
26
|
-
if (segments.length === 0 ||
|
|
27
|
-
segments.join("/") !== raw ||
|
|
28
|
-
raw.normalize("NFC") !== raw ||
|
|
29
|
-
segments.some((segment) => /[ .]$/u.test(segment))) {
|
|
30
|
-
throw new FsSafeError("invalid-path", "store key must use one canonical relative spelling");
|
|
31
|
-
}
|
|
32
|
-
return raw;
|
|
33
|
-
}
|
|
34
|
-
function resolveStorePath(rootDir, relativePath) {
|
|
35
|
-
return resolveSafeRelativePath(rootDir, assertRelativePath(relativePath));
|
|
36
|
-
}
|
|
37
21
|
function isNotFound(error) {
|
|
38
22
|
return error instanceof FsSafeError ? error.code === "not-found" : isNotFoundPathError(error);
|
|
39
23
|
}
|
package/dist/filename.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"AAmCA,yFAAyF;AACzF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CA8BT;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAoBxF"}
|
package/dist/filename.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { WINDOWS_RESERVED_DEVICE_NAMES } from "./device-path.js";
|
|
3
|
+
import { maxNormalizedUtf8Bytes } from "./unicode-path.js";
|
|
3
4
|
const INVALID_FILE_NAME_CHARACTERS = /[\u0000-\u001f\u007f-\u009f<>:"/\\|?*]/g;
|
|
4
5
|
function trimWindowsIgnoredSuffix(value) {
|
|
5
6
|
let end = value.length;
|
|
@@ -24,7 +25,7 @@ function suffixWindowsReservedDeviceName(fileName) {
|
|
|
24
25
|
}
|
|
25
26
|
const PORTABLE_FILE_NAME_BYTES = 255;
|
|
26
27
|
function normalizedFileNameBytes(value) {
|
|
27
|
-
return
|
|
28
|
+
return maxNormalizedUtf8Bytes(value, true);
|
|
28
29
|
}
|
|
29
30
|
/** Keeps short names exact and trims only the filename tail of a composite temp name. */
|
|
30
31
|
export function fitFileNameToPortableComponent(params) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"AA2BA;;;;;GAKG;AACH,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClE,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAwDlB"}
|
package/dist/guarded-mkdir.js
CHANGED
|
@@ -5,13 +5,14 @@ import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./director
|
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { isNotFoundPathError, isPathRelativeEscape } from "./path.js";
|
|
7
7
|
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
8
|
+
import { realpathSync } from "./realpath.js";
|
|
8
9
|
function isSameOrChildPath(candidate, parent) {
|
|
9
10
|
const parentPrefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`;
|
|
10
11
|
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
11
12
|
}
|
|
12
13
|
async function realpathOrThrowNotFile(target) {
|
|
13
14
|
try {
|
|
14
|
-
return path.resolve(
|
|
15
|
+
return path.resolve(realpathSync.native(target));
|
|
15
16
|
}
|
|
16
17
|
catch (error) {
|
|
17
18
|
if (isNotFoundPathError(error)) {
|
|
@@ -30,7 +31,7 @@ async function realpathOrThrowNotFile(target) {
|
|
|
30
31
|
*/
|
|
31
32
|
export async function mkdirPathComponentsWithGuards(params) {
|
|
32
33
|
const root = path.resolve(params.rootReal);
|
|
33
|
-
const rootCanonical = path.resolve(
|
|
34
|
+
const rootCanonical = path.resolve(realpathSync.native(root));
|
|
34
35
|
const target = path.resolve(params.targetPath);
|
|
35
36
|
const relative = path.relative(root, target);
|
|
36
37
|
if (isPathRelativeEscape(relative)) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guest-dispatch-python.d.ts","sourceRoot":"","sources":["../src/guest-dispatch-python.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gCAAgC,QAoHjC,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export const GUEST_FILESYSTEM_DISPATCH_PYTHON = [
|
|
2
|
+
"if operation == 'copy':",
|
|
3
|
+
" src_root_fd = open_dir(sys.argv[2])",
|
|
4
|
+
" dst_root_fd = open_dir(sys.argv[5])",
|
|
5
|
+
" src_parent_fd = None",
|
|
6
|
+
" dst_parent_fd = None",
|
|
7
|
+
" try:",
|
|
8
|
+
" src_parent_fd = walk_dir(src_root_fd, sys.argv[3], False)",
|
|
9
|
+
" dst_parent_fd = walk_dir(dst_root_fd, sys.argv[6], sys.argv[8] == '1')",
|
|
10
|
+
" copy_file_atomic(src_parent_fd, sys.argv[4], dst_parent_fd, sys.argv[7])",
|
|
11
|
+
" finally:",
|
|
12
|
+
" if src_parent_fd is not None:",
|
|
13
|
+
" os.close(src_parent_fd)",
|
|
14
|
+
" if dst_parent_fd is not None:",
|
|
15
|
+
" os.close(dst_parent_fd)",
|
|
16
|
+
" os.close(src_root_fd)",
|
|
17
|
+
" os.close(dst_root_fd)",
|
|
18
|
+
"elif operation == 'write':",
|
|
19
|
+
" root_fd = open_dir(sys.argv[2])",
|
|
20
|
+
" parent_fd = None",
|
|
21
|
+
" try:",
|
|
22
|
+
" parent_fd = walk_dir(root_fd, sys.argv[3], sys.argv[5] == '1')",
|
|
23
|
+
" write_atomic(parent_fd, sys.argv[4], sys.stdin.buffer)",
|
|
24
|
+
" finally:",
|
|
25
|
+
" if parent_fd is not None:",
|
|
26
|
+
" os.close(parent_fd)",
|
|
27
|
+
" os.close(root_fd)",
|
|
28
|
+
"elif operation == 'create':",
|
|
29
|
+
" root_fd = open_dir(sys.argv[2])",
|
|
30
|
+
" parent_fd = None",
|
|
31
|
+
" try:",
|
|
32
|
+
" parent_fd = walk_dir(root_fd, sys.argv[3], sys.argv[5] == '1')",
|
|
33
|
+
" try:",
|
|
34
|
+
" create_exclusive(parent_fd, sys.argv[4], sys.stdin.buffer)",
|
|
35
|
+
" except FileExistsError:",
|
|
36
|
+
" sys.exit(SANDBOX_CREATE_EXISTS_EXIT_CODE)",
|
|
37
|
+
" finally:",
|
|
38
|
+
" if parent_fd is not None:",
|
|
39
|
+
" os.close(parent_fd)",
|
|
40
|
+
" os.close(root_fd)",
|
|
41
|
+
"elif operation == 'read':",
|
|
42
|
+
" root_fd = open_dir(sys.argv[2])",
|
|
43
|
+
" parent_fd = None",
|
|
44
|
+
" try:",
|
|
45
|
+
" parent_fd = walk_dir(root_fd, sys.argv[3], False)",
|
|
46
|
+
" if len(sys.argv) > 5:",
|
|
47
|
+
" read_file_bounded(parent_fd, sys.argv[4], int(sys.argv[5]))",
|
|
48
|
+
" else:",
|
|
49
|
+
" read_file(parent_fd, sys.argv[4])",
|
|
50
|
+
" except FileNotFoundError:",
|
|
51
|
+
" sys.exit(SANDBOX_READ_NOT_FOUND_EXIT_CODE)",
|
|
52
|
+
" finally:",
|
|
53
|
+
" if parent_fd is not None:",
|
|
54
|
+
" os.close(parent_fd)",
|
|
55
|
+
" os.close(root_fd)",
|
|
56
|
+
"elif operation == 'readdir':",
|
|
57
|
+
" root_fd = open_dir(sys.argv[2])",
|
|
58
|
+
" target_fd = None",
|
|
59
|
+
" try:",
|
|
60
|
+
" target_fd = walk_dir(root_fd, sys.argv[3], False)",
|
|
61
|
+
" with os.scandir(target_fd) as entries:",
|
|
62
|
+
" print(json.dumps([{'name': entry.name, 'isDirectory': entry.is_dir(follow_symlinks=False)} for entry in entries]))",
|
|
63
|
+
" finally:",
|
|
64
|
+
" if target_fd is not None:",
|
|
65
|
+
" os.close(target_fd)",
|
|
66
|
+
" os.close(root_fd)",
|
|
67
|
+
"elif operation == 'mkdirp':",
|
|
68
|
+
" root_fd = open_dir(sys.argv[2])",
|
|
69
|
+
" target_fd = None",
|
|
70
|
+
" try:",
|
|
71
|
+
" target_fd = walk_dir(root_fd, sys.argv[3], True)",
|
|
72
|
+
" os.fsync(target_fd)",
|
|
73
|
+
" finally:",
|
|
74
|
+
" if target_fd is not None:",
|
|
75
|
+
" os.close(target_fd)",
|
|
76
|
+
" os.close(root_fd)",
|
|
77
|
+
"elif operation == 'remove':",
|
|
78
|
+
" root_fd = open_dir(sys.argv[2])",
|
|
79
|
+
" parent_fd = None",
|
|
80
|
+
" try:",
|
|
81
|
+
" parent_fd = walk_dir(root_fd, sys.argv[3], False)",
|
|
82
|
+
" try:",
|
|
83
|
+
" if sys.argv[5] == '1':",
|
|
84
|
+
" remove_tree(parent_fd, sys.argv[4])",
|
|
85
|
+
" else:",
|
|
86
|
+
" entry_stat = os.lstat(sys.argv[4], dir_fd=parent_fd)",
|
|
87
|
+
" if stat.S_ISDIR(entry_stat.st_mode) and not stat.S_ISLNK(entry_stat.st_mode):",
|
|
88
|
+
" os.rmdir(sys.argv[4], dir_fd=parent_fd)",
|
|
89
|
+
" else:",
|
|
90
|
+
" os.unlink(sys.argv[4], dir_fd=parent_fd)",
|
|
91
|
+
" os.fsync(parent_fd)",
|
|
92
|
+
" except FileNotFoundError:",
|
|
93
|
+
" if sys.argv[6] != '1':",
|
|
94
|
+
" raise",
|
|
95
|
+
" finally:",
|
|
96
|
+
" if parent_fd is not None:",
|
|
97
|
+
" os.close(parent_fd)",
|
|
98
|
+
" os.close(root_fd)",
|
|
99
|
+
"elif operation == 'rename':",
|
|
100
|
+
" src_root_fd = open_dir(sys.argv[2])",
|
|
101
|
+
" dst_root_fd = open_dir(sys.argv[5])",
|
|
102
|
+
" src_parent_fd = None",
|
|
103
|
+
" dst_parent_fd = None",
|
|
104
|
+
" try:",
|
|
105
|
+
" src_parent_fd = walk_dir(src_root_fd, sys.argv[3], False)",
|
|
106
|
+
" dst_parent_fd = walk_dir(dst_root_fd, sys.argv[6], sys.argv[8] == '1')",
|
|
107
|
+
" move_entry(src_parent_fd, sys.argv[4], dst_parent_fd, sys.argv[7])",
|
|
108
|
+
" finally:",
|
|
109
|
+
" if src_parent_fd is not None:",
|
|
110
|
+
" os.close(src_parent_fd)",
|
|
111
|
+
" if dst_parent_fd is not None:",
|
|
112
|
+
" os.close(dst_parent_fd)",
|
|
113
|
+
" os.close(src_root_fd)",
|
|
114
|
+
" os.close(dst_root_fd)",
|
|
115
|
+
"else:",
|
|
116
|
+
" raise RuntimeError('unknown sandbox mutation operation: ' + operation)",
|
|
117
|
+
].join("\n");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guest-native-python.d.ts","sourceRoot":"","sources":["../src/guest-native-python.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sCAAsC,QA+BvC,CAAC;AAEb,eAAO,MAAM,yCAAyC,QAgD1C,CAAC;AAEb,eAAO,MAAM,wCAAwC,QAqDzC,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export const GUEST_FILESYSTEM_CREATE_STAGING_PYTHON = [
|
|
2
|
+
"def create_staging_dir(parent_fd):",
|
|
3
|
+
" # This helper guarantees descriptor-relative confinement and no-replace",
|
|
4
|
+
" # publication, not content integrity against same-UID peers; they can",
|
|
5
|
+
" # also rewrite the destination immediately after publication.",
|
|
6
|
+
" prefix = '.openclaw-create-'",
|
|
7
|
+
" for _ in range(128):",
|
|
8
|
+
" candidate = prefix + secrets.token_hex(6)",
|
|
9
|
+
" try:",
|
|
10
|
+
" os.mkdir(candidate, 0o700, dir_fd=parent_fd)",
|
|
11
|
+
" except FileExistsError:",
|
|
12
|
+
" continue",
|
|
13
|
+
" created_identity = entry_identity(os.lstat(candidate, dir_fd=parent_fd))",
|
|
14
|
+
" staging_fd = None",
|
|
15
|
+
" try:",
|
|
16
|
+
" staging_fd = open_dir(candidate, dir_fd=parent_fd)",
|
|
17
|
+
" if not same_identity(created_identity, os.fstat(staging_fd)):",
|
|
18
|
+
" raise OSError(errno.ESTALE, 'create staging directory changed', candidate)",
|
|
19
|
+
" return candidate, staging_fd",
|
|
20
|
+
" except Exception:",
|
|
21
|
+
" if staging_fd is not None:",
|
|
22
|
+
" os.close(staging_fd)",
|
|
23
|
+
" try:",
|
|
24
|
+
" current = os.lstat(candidate, dir_fd=parent_fd)",
|
|
25
|
+
" if same_identity(created_identity, current):",
|
|
26
|
+
" os.rmdir(candidate, dir_fd=parent_fd)",
|
|
27
|
+
" os.fsync(parent_fd)",
|
|
28
|
+
" except FileNotFoundError:",
|
|
29
|
+
" pass",
|
|
30
|
+
" raise",
|
|
31
|
+
" raise RuntimeError('failed to allocate sandbox create staging directory')",
|
|
32
|
+
].join("\n");
|
|
33
|
+
export const GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON = [
|
|
34
|
+
"def rename_no_replace(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
|
|
35
|
+
" libc = ctypes.CDLL(None, use_errno=True)",
|
|
36
|
+
" is_linux = sys.platform.startswith('linux')",
|
|
37
|
+
" if is_linux:",
|
|
38
|
+
" rename_fn = getattr(libc, 'renameat2', None)",
|
|
39
|
+
" if rename_fn is None:",
|
|
40
|
+
" os.link(",
|
|
41
|
+
" src_basename,",
|
|
42
|
+
" dst_basename,",
|
|
43
|
+
" src_dir_fd=src_parent_fd,",
|
|
44
|
+
" dst_dir_fd=dst_parent_fd,",
|
|
45
|
+
" follow_symlinks=False,",
|
|
46
|
+
" )",
|
|
47
|
+
" return",
|
|
48
|
+
" flags = 1 # RENAME_NOREPLACE",
|
|
49
|
+
" elif sys.platform == 'darwin':",
|
|
50
|
+
" rename_fn = getattr(libc, 'renameatx_np', None)",
|
|
51
|
+
" flags = 0x00000004 # RENAME_EXCL",
|
|
52
|
+
" else:",
|
|
53
|
+
" rename_fn = None",
|
|
54
|
+
" flags = 0",
|
|
55
|
+
" if rename_fn is None:",
|
|
56
|
+
" raise OSError(errno.ENOSYS, 'atomic no-replace rename is unavailable')",
|
|
57
|
+
" rename_fn.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint]",
|
|
58
|
+
" rename_fn.restype = ctypes.c_int",
|
|
59
|
+
" result = rename_fn(",
|
|
60
|
+
" src_parent_fd,",
|
|
61
|
+
" os.fsencode(src_basename),",
|
|
62
|
+
" dst_parent_fd,",
|
|
63
|
+
" os.fsencode(dst_basename),",
|
|
64
|
+
" flags,",
|
|
65
|
+
" )",
|
|
66
|
+
" if result != 0:",
|
|
67
|
+
" error_code = ctypes.get_errno()",
|
|
68
|
+
" unsupported_codes = {errno.ENOSYS, errno.EINVAL, errno.ENOTSUP}",
|
|
69
|
+
" if hasattr(errno, 'EOPNOTSUPP'):",
|
|
70
|
+
" unsupported_codes.add(errno.EOPNOTSUPP)",
|
|
71
|
+
" if is_linux and error_code in unsupported_codes:",
|
|
72
|
+
" os.link(",
|
|
73
|
+
" src_basename,",
|
|
74
|
+
" dst_basename,",
|
|
75
|
+
" src_dir_fd=src_parent_fd,",
|
|
76
|
+
" dst_dir_fd=dst_parent_fd,",
|
|
77
|
+
" follow_symlinks=False,",
|
|
78
|
+
" )",
|
|
79
|
+
" return",
|
|
80
|
+
" raise OSError(error_code, os.strerror(error_code), dst_basename)",
|
|
81
|
+
].join("\n");
|
|
82
|
+
export const GUEST_FILESYSTEM_CREATE_EXCLUSIVE_PYTHON = [
|
|
83
|
+
"def create_exclusive(parent_fd, basename, stdin_buffer):",
|
|
84
|
+
" staging_fd = None",
|
|
85
|
+
" staging_name = None",
|
|
86
|
+
" temp_fd = None",
|
|
87
|
+
" temp_name = 'payload'",
|
|
88
|
+
" temp_inode = None",
|
|
89
|
+
" try:",
|
|
90
|
+
" try:",
|
|
91
|
+
" os.lstat(basename, dir_fd=parent_fd)",
|
|
92
|
+
" except FileNotFoundError:",
|
|
93
|
+
" pass",
|
|
94
|
+
" else:",
|
|
95
|
+
" raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), basename)",
|
|
96
|
+
" staging_name, staging_fd = create_staging_dir(parent_fd)",
|
|
97
|
+
" temp_fd = os.open(temp_name, WRITE_FLAGS, 0o600, dir_fd=staging_fd)",
|
|
98
|
+
" while True:",
|
|
99
|
+
" chunk = stdin_buffer.read(65536)",
|
|
100
|
+
" if not chunk:",
|
|
101
|
+
" break",
|
|
102
|
+
" write_all(temp_fd, chunk)",
|
|
103
|
+
" # exclusive create payload is durable before publication",
|
|
104
|
+
" os.fsync(temp_fd)",
|
|
105
|
+
" temp_inode = inode_identity(os.fstat(temp_fd))",
|
|
106
|
+
" # Publish with a native atomic no-replace rename.",
|
|
107
|
+
" rename_no_replace(staging_fd, temp_name, parent_fd, basename)",
|
|
108
|
+
" target_stat = os.lstat(basename, dir_fd=parent_fd)",
|
|
109
|
+
" if temp_inode != inode_identity(target_stat):",
|
|
110
|
+
" raise OSError(errno.ESTALE, 'exclusive publication source changed', basename)",
|
|
111
|
+
" os.fsync(parent_fd)",
|
|
112
|
+
" finally:",
|
|
113
|
+
" if temp_fd is not None:",
|
|
114
|
+
" os.close(temp_fd)",
|
|
115
|
+
" if staging_fd is not None:",
|
|
116
|
+
" # Cleanup stays relative to the private pinned directory, so a",
|
|
117
|
+
" # parent-path substitution cannot redirect payload deletion.",
|
|
118
|
+
" try:",
|
|
119
|
+
" os.unlink(temp_name, dir_fd=staging_fd)",
|
|
120
|
+
" except FileNotFoundError:",
|
|
121
|
+
" pass",
|
|
122
|
+
" staging_identity = entry_identity(os.fstat(staging_fd))",
|
|
123
|
+
" os.close(staging_fd)",
|
|
124
|
+
" staging_removed = False",
|
|
125
|
+
" if staging_name is not None:",
|
|
126
|
+
" try:",
|
|
127
|
+
" current_staging = os.lstat(staging_name, dir_fd=parent_fd)",
|
|
128
|
+
" if same_identity(staging_identity, current_staging):",
|
|
129
|
+
" os.rmdir(staging_name, dir_fd=parent_fd)",
|
|
130
|
+
" staging_removed = True",
|
|
131
|
+
" except FileNotFoundError:",
|
|
132
|
+
" pass",
|
|
133
|
+
" if staging_removed:",
|
|
134
|
+
" os.fsync(parent_fd)",
|
|
135
|
+
].join("\n");
|
package/dist/guest.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON } from "./guest-native-python.js";
|
|
2
|
+
/** Exclusive create found an existing destination, including a lost publication race. */
|
|
3
|
+
export declare const GUEST_FILESYSTEM_CREATE_EXISTS_EXIT_CODE = 17;
|
|
4
|
+
/** Read found a missing parent or file after the admitted root was opened. */
|
|
5
|
+
export declare const GUEST_FILESYSTEM_READ_NOT_FOUND_EXIT_CODE = 2;
|
|
6
|
+
/** Python 3 source for the descriptor-relative guest filesystem protocol. See docs/guest.md. */
|
|
7
|
+
export declare const GUEST_FILESYSTEM_PYTHON: string;
|
|
8
|
+
export { GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON };
|
|
9
|
+
//# sourceMappingURL=guest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guest.d.ts","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,yCAAyC,EAC1C,MAAM,0BAA0B,CAAC;AAElC,yFAAyF;AACzF,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAC3D,8EAA8E;AAC9E,eAAO,MAAM,yCAAyC,IAAI,CAAC;AAE3D,gGAAgG;AAChG,eAAO,MAAM,uBAAuB,QAmZxB,CAAC;AAEb,OAAO,EAAE,yCAAyC,EAAE,CAAC"}
|