@openclaw/fs-safe 0.9.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 +134 -0
- package/LICENSE +1 -0
- package/README.md +52 -4
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +3 -2
- package/dist/advanced.d.ts +5 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +5 -0
- package/dist/archive-crc32.d.ts.map +1 -1
- package/dist/archive-crc32.js +6 -1
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +3 -4
- 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 +3 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +23 -3
- 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 +16 -13
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -6
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +83 -74
- 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 +11 -4
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +23 -10
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +18 -14
- 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 +12 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +82 -45
- package/dist/clone-metadata.d.ts +19 -0
- package/dist/clone-metadata.d.ts.map +1 -0
- package/dist/clone-metadata.js +32 -0
- package/dist/copy-file-input.d.ts +22 -0
- package/dist/copy-file-input.d.ts.map +1 -0
- package/dist/copy-file-input.js +69 -0
- package/dist/copy-policy.d.ts +3 -0
- package/dist/copy-policy.d.ts.map +1 -0
- package/dist/copy-policy.js +8 -0
- package/dist/copy-publication.d.ts +10 -1
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +27 -0
- package/dist/copy-tree-portable.d.ts +9 -0
- package/dist/copy-tree-portable.d.ts.map +1 -0
- package/dist/copy-tree-portable.js +222 -0
- package/dist/copy.d.ts +16 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +125 -0
- 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/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +4 -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 +9 -2
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +135 -39
- 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 +4 -13
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +4 -2
- package/dist/guarded-mutation.d.ts +2 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +8 -4
- 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/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +9 -7
- 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 +5 -8
- 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 +19 -18
- 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 +24 -25
- package/dist/move-path-stage.d.ts +8 -0
- package/dist/move-path-stage.d.ts.map +1 -0
- package/dist/move-path-stage.js +56 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +30 -34
- package/dist/mutation-authority.d.ts +9 -0
- package/dist/mutation-authority.d.ts.map +1 -0
- package/dist/mutation-authority.js +36 -0
- package/dist/native-binding.d.ts +29 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +11 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +19 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +3 -1
- package/dist/native-staged-file.d.ts +3 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +35 -8
- 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 +5 -2
- package/dist/permissions-windows.d.ts +1 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +85 -53
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +3 -1
- package/dist/pinned-operation.js +1 -1
- package/dist/pinned-write.d.ts +7 -3
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +51 -30
- package/dist/positional-read.d.ts +9 -0
- package/dist/positional-read.d.ts.map +1 -0
- package/dist/positional-read.js +36 -0
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +6 -4
- package/dist/publish-copy-stage.d.ts +13 -0
- package/dist/publish-copy-stage.d.ts.map +1 -0
- package/dist/publish-copy-stage.js +47 -0
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +6 -5
- 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-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +6 -2
- 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 +3 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +53 -28
- 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 +26 -0
- package/dist/root-directory-list.d.ts.map +1 -0
- package/dist/root-directory-list.js +219 -0
- 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 +6 -5
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +16 -12
- package/dist/root-file.d.ts +2 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +12 -4
- package/dist/root-impl.d.ts +17 -51
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +358 -234
- package/dist/root-options.d.ts +77 -0
- package/dist/root-options.d.ts.map +1 -0
- package/dist/root-options.js +18 -0
- package/dist/root-path-existing.d.ts +5 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +59 -3
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +3 -2
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +74 -162
- 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 +14 -0
- package/dist/root-symlink-policy.d.ts.map +1 -0
- package/dist/root-symlink-policy.js +34 -0
- package/dist/root-walk.d.ts +5 -4
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +99 -50
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +3 -5
- package/dist/root.d.ts +4 -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 +25 -8
- 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 +17 -5
- 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 +20 -1
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +4 -3
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +3 -2
- 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/timing.d.ts +1 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +25 -6
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +9 -7
- 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 +9 -28
- package/dist/windows-owner.d.ts +9 -12
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +24 -58
- package/dist/write-file-handle.d.ts +7 -0
- package/dist/write-file-handle.d.ts.map +1 -0
- package/dist/write-file-handle.js +26 -0
- package/docs/advanced.md +22 -4
- package/docs/archive.md +44 -9
- package/docs/atomic.md +24 -1
- package/docs/config.md +1 -0
- package/docs/contributing.md +36 -1
- package/docs/copy.md +155 -0
- package/docs/directory-identity.md +85 -0
- package/docs/durability.md +53 -3
- package/docs/entries.md +109 -0
- package/docs/errors.md +4 -4
- 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/local-roots.md +8 -1
- package/docs/native-helper.md +10 -3
- package/docs/native.md +18 -1
- package/docs/output.md +32 -6
- package/docs/path-case.md +64 -0
- package/docs/path-scope.md +1 -1
- package/docs/permissions.md +29 -10
- package/docs/positional-read.md +63 -0
- package/docs/public-api.md +31 -2
- package/docs/root.md +196 -6
- package/docs/secure-file.md +2 -0
- package/docs/security-model.md +14 -0
- package/docs/sidecar-lock.md +12 -3
- package/docs/temp.md +35 -6
- package/docs/timing.md +2 -0
- package/docs/types.md +19 -12
- package/docs/walk.md +54 -5
- package/docs/writing.md +173 -5
- package/package.json +19 -8
package/dist/guest.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
// Adapted from OpenClaw's guest filesystem program. Copyright OpenClaw Foundation; MIT.
|
|
2
|
+
import { GUEST_FILESYSTEM_DISPATCH_PYTHON } from "./guest-dispatch-python.js";
|
|
3
|
+
import { GUEST_FILESYSTEM_CREATE_EXCLUSIVE_PYTHON, GUEST_FILESYSTEM_CREATE_STAGING_PYTHON, GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON, } from "./guest-native-python.js";
|
|
4
|
+
/** Exclusive create found an existing destination, including a lost publication race. */
|
|
5
|
+
export const GUEST_FILESYSTEM_CREATE_EXISTS_EXIT_CODE = 17;
|
|
6
|
+
/** Read found a missing parent or file after the admitted root was opened. */
|
|
7
|
+
export const GUEST_FILESYSTEM_READ_NOT_FOUND_EXIT_CODE = 2;
|
|
8
|
+
/** Python 3 source for the descriptor-relative guest filesystem protocol. See docs/guest.md. */
|
|
9
|
+
export const GUEST_FILESYSTEM_PYTHON = [
|
|
10
|
+
`SANDBOX_CREATE_EXISTS_EXIT_CODE = ${GUEST_FILESYSTEM_CREATE_EXISTS_EXIT_CODE}`,
|
|
11
|
+
`SANDBOX_READ_NOT_FOUND_EXIT_CODE = ${GUEST_FILESYSTEM_READ_NOT_FOUND_EXIT_CODE}`,
|
|
12
|
+
"import ctypes",
|
|
13
|
+
"import errno",
|
|
14
|
+
"import json",
|
|
15
|
+
"import os",
|
|
16
|
+
"import secrets",
|
|
17
|
+
"import stat",
|
|
18
|
+
"import sys",
|
|
19
|
+
"",
|
|
20
|
+
"operation = sys.argv[1]",
|
|
21
|
+
"",
|
|
22
|
+
"DIR_FLAGS = os.O_RDONLY",
|
|
23
|
+
"if hasattr(os, 'O_DIRECTORY'):",
|
|
24
|
+
" DIR_FLAGS |= os.O_DIRECTORY",
|
|
25
|
+
"if hasattr(os, 'O_NOFOLLOW'):",
|
|
26
|
+
" DIR_FLAGS |= os.O_NOFOLLOW",
|
|
27
|
+
"",
|
|
28
|
+
"READ_FLAGS = os.O_RDONLY",
|
|
29
|
+
"if hasattr(os, 'O_NOFOLLOW'):",
|
|
30
|
+
" READ_FLAGS |= os.O_NOFOLLOW",
|
|
31
|
+
"if hasattr(os, 'O_NONBLOCK'):",
|
|
32
|
+
" READ_FLAGS |= os.O_NONBLOCK",
|
|
33
|
+
"",
|
|
34
|
+
"WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL",
|
|
35
|
+
"if hasattr(os, 'O_NOFOLLOW'):",
|
|
36
|
+
" WRITE_FLAGS |= os.O_NOFOLLOW",
|
|
37
|
+
"",
|
|
38
|
+
"def split_relative(path_value):",
|
|
39
|
+
" segments = []",
|
|
40
|
+
" for segment in path_value.split('/'):",
|
|
41
|
+
" if not segment or segment == '.':",
|
|
42
|
+
" continue",
|
|
43
|
+
" if segment == '..':",
|
|
44
|
+
" raise OSError(errno.EPERM, 'path traversal is not allowed', segment)",
|
|
45
|
+
" segments.append(segment)",
|
|
46
|
+
" return segments",
|
|
47
|
+
"",
|
|
48
|
+
"def open_dir(path_value, dir_fd=None):",
|
|
49
|
+
" return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)",
|
|
50
|
+
"",
|
|
51
|
+
"def walk_dir(root_fd, rel_path, mkdir_enabled):",
|
|
52
|
+
" current_fd = os.dup(root_fd)",
|
|
53
|
+
" try:",
|
|
54
|
+
" for segment in split_relative(rel_path):",
|
|
55
|
+
" try:",
|
|
56
|
+
" next_fd = open_dir(segment, dir_fd=current_fd)",
|
|
57
|
+
" except FileNotFoundError:",
|
|
58
|
+
" if not mkdir_enabled:",
|
|
59
|
+
" raise",
|
|
60
|
+
" os.mkdir(segment, 0o777, dir_fd=current_fd)",
|
|
61
|
+
" next_fd = open_dir(segment, dir_fd=current_fd)",
|
|
62
|
+
" os.close(current_fd)",
|
|
63
|
+
" current_fd = next_fd",
|
|
64
|
+
" return current_fd",
|
|
65
|
+
" except Exception:",
|
|
66
|
+
" os.close(current_fd)",
|
|
67
|
+
" raise",
|
|
68
|
+
"",
|
|
69
|
+
"def create_temp_file(parent_fd):",
|
|
70
|
+
" prefix = '.openclaw-write-'",
|
|
71
|
+
" for _ in range(128):",
|
|
72
|
+
" candidate = prefix + secrets.token_hex(6)",
|
|
73
|
+
" try:",
|
|
74
|
+
" fd = os.open(candidate, WRITE_FLAGS, 0o600, dir_fd=parent_fd)",
|
|
75
|
+
" return candidate, fd",
|
|
76
|
+
" except FileExistsError:",
|
|
77
|
+
" continue",
|
|
78
|
+
" raise RuntimeError('failed to allocate sandbox temp file')",
|
|
79
|
+
"",
|
|
80
|
+
"def create_temp_dir(parent_fd, mode):",
|
|
81
|
+
" prefix = '.openclaw-move-'",
|
|
82
|
+
" for _ in range(128):",
|
|
83
|
+
" candidate = prefix + secrets.token_hex(6)",
|
|
84
|
+
" try:",
|
|
85
|
+
" os.mkdir(candidate, mode, dir_fd=parent_fd)",
|
|
86
|
+
" return candidate",
|
|
87
|
+
" except FileExistsError:",
|
|
88
|
+
" continue",
|
|
89
|
+
" raise RuntimeError('failed to allocate sandbox temp directory')",
|
|
90
|
+
"",
|
|
91
|
+
GUEST_FILESYSTEM_CREATE_STAGING_PYTHON,
|
|
92
|
+
"",
|
|
93
|
+
"def existing_regular_file_mode(parent_fd, basename):",
|
|
94
|
+
" try:",
|
|
95
|
+
" target_stat = os.lstat(basename, dir_fd=parent_fd)",
|
|
96
|
+
" except FileNotFoundError:",
|
|
97
|
+
" return None",
|
|
98
|
+
" if stat.S_ISREG(target_stat.st_mode):",
|
|
99
|
+
" return stat.S_IMODE(target_stat.st_mode)",
|
|
100
|
+
" return None",
|
|
101
|
+
"",
|
|
102
|
+
"def write_all(file_fd, data):",
|
|
103
|
+
" view = memoryview(data)",
|
|
104
|
+
" while view:",
|
|
105
|
+
" written = os.write(file_fd, view)",
|
|
106
|
+
" if written <= 0:",
|
|
107
|
+
" raise OSError(errno.EIO, 'failed to write copied file')",
|
|
108
|
+
" view = view[written:]",
|
|
109
|
+
"",
|
|
110
|
+
GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON,
|
|
111
|
+
"",
|
|
112
|
+
"def copy_regular_file(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
|
|
113
|
+
" src_fd = os.open(src_basename, READ_FLAGS, dir_fd=src_parent_fd)",
|
|
114
|
+
" dst_fd = None",
|
|
115
|
+
" destination_created = False",
|
|
116
|
+
" copy_completed = False",
|
|
117
|
+
" try:",
|
|
118
|
+
" src_stat = os.fstat(src_fd)",
|
|
119
|
+
" if not stat.S_ISREG(src_stat.st_mode):",
|
|
120
|
+
" raise OSError(errno.EPERM, 'only regular files are allowed', src_basename)",
|
|
121
|
+
" if src_stat.st_nlink > 1:",
|
|
122
|
+
" raise OSError(errno.EPERM, 'hardlinked file is not allowed', src_basename)",
|
|
123
|
+
" dst_fd = os.open(dst_basename, WRITE_FLAGS, stat.S_IMODE(src_stat.st_mode), dir_fd=dst_parent_fd)",
|
|
124
|
+
" destination_created = True",
|
|
125
|
+
" while True:",
|
|
126
|
+
" chunk = os.read(src_fd, 65536)",
|
|
127
|
+
" if not chunk:",
|
|
128
|
+
" break",
|
|
129
|
+
" write_all(dst_fd, chunk)",
|
|
130
|
+
" try:",
|
|
131
|
+
" os.fchmod(dst_fd, stat.S_IMODE(src_stat.st_mode))",
|
|
132
|
+
" except AttributeError:",
|
|
133
|
+
" pass",
|
|
134
|
+
" os.fsync(dst_fd)",
|
|
135
|
+
" copy_completed = True",
|
|
136
|
+
" return entry_identity(src_stat)",
|
|
137
|
+
" finally:",
|
|
138
|
+
" if dst_fd is not None:",
|
|
139
|
+
" os.close(dst_fd)",
|
|
140
|
+
" if destination_created and not copy_completed:",
|
|
141
|
+
" try:",
|
|
142
|
+
" os.unlink(dst_basename, dir_fd=dst_parent_fd)",
|
|
143
|
+
" except FileNotFoundError:",
|
|
144
|
+
" pass",
|
|
145
|
+
" os.close(src_fd)",
|
|
146
|
+
"",
|
|
147
|
+
"def copy_file_atomic(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
|
|
148
|
+
" prefix = '.openclaw-copy-'",
|
|
149
|
+
" temp_name = None",
|
|
150
|
+
" try:",
|
|
151
|
+
" for _ in range(128):",
|
|
152
|
+
" candidate = prefix + secrets.token_hex(6)",
|
|
153
|
+
" try:",
|
|
154
|
+
" copy_regular_file(src_parent_fd, src_basename, dst_parent_fd, candidate)",
|
|
155
|
+
" temp_name = candidate",
|
|
156
|
+
" break",
|
|
157
|
+
" except FileExistsError:",
|
|
158
|
+
" continue",
|
|
159
|
+
" if temp_name is None:",
|
|
160
|
+
" raise RuntimeError('failed to allocate sandbox copy temp file')",
|
|
161
|
+
" os.replace(temp_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
|
|
162
|
+
" temp_name = None",
|
|
163
|
+
" os.fsync(dst_parent_fd)",
|
|
164
|
+
" finally:",
|
|
165
|
+
" if temp_name is not None:",
|
|
166
|
+
" try:",
|
|
167
|
+
" os.unlink(temp_name, dir_fd=dst_parent_fd)",
|
|
168
|
+
" except FileNotFoundError:",
|
|
169
|
+
" pass",
|
|
170
|
+
"",
|
|
171
|
+
"def write_atomic(parent_fd, basename, stdin_buffer):",
|
|
172
|
+
" target_mode = existing_regular_file_mode(parent_fd, basename)",
|
|
173
|
+
" temp_fd = None",
|
|
174
|
+
" temp_name = None",
|
|
175
|
+
" try:",
|
|
176
|
+
" temp_name, temp_fd = create_temp_file(parent_fd)",
|
|
177
|
+
" while True:",
|
|
178
|
+
" chunk = stdin_buffer.read(65536)",
|
|
179
|
+
" if not chunk:",
|
|
180
|
+
" break",
|
|
181
|
+
" write_all(temp_fd, chunk)",
|
|
182
|
+
" if target_mode is not None:",
|
|
183
|
+
" try:",
|
|
184
|
+
" os.fchmod(temp_fd, target_mode)",
|
|
185
|
+
" except AttributeError:",
|
|
186
|
+
" pass",
|
|
187
|
+
" os.fsync(temp_fd)",
|
|
188
|
+
" os.close(temp_fd)",
|
|
189
|
+
" temp_fd = None",
|
|
190
|
+
" os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)",
|
|
191
|
+
" temp_name = None",
|
|
192
|
+
" os.fsync(parent_fd)",
|
|
193
|
+
" finally:",
|
|
194
|
+
" if temp_fd is not None:",
|
|
195
|
+
" os.close(temp_fd)",
|
|
196
|
+
" if temp_name is not None:",
|
|
197
|
+
" try:",
|
|
198
|
+
" os.unlink(temp_name, dir_fd=parent_fd)",
|
|
199
|
+
" except FileNotFoundError:",
|
|
200
|
+
" pass",
|
|
201
|
+
"",
|
|
202
|
+
GUEST_FILESYSTEM_CREATE_EXCLUSIVE_PYTHON,
|
|
203
|
+
"",
|
|
204
|
+
"def read_file_impl(parent_fd, basename, max_bytes):",
|
|
205
|
+
" file_fd = os.open(basename, READ_FLAGS, dir_fd=parent_fd)",
|
|
206
|
+
" try:",
|
|
207
|
+
" file_stat = os.fstat(file_fd)",
|
|
208
|
+
" if not stat.S_ISREG(file_stat.st_mode):",
|
|
209
|
+
" raise OSError(errno.EPERM, 'only regular files are allowed', basename)",
|
|
210
|
+
" if file_stat.st_nlink > 1:",
|
|
211
|
+
" raise OSError(errno.EPERM, 'hardlinked file is not allowed', basename)",
|
|
212
|
+
" if max_bytes is not None and file_stat.st_size > max_bytes:",
|
|
213
|
+
" raise OSError(errno.EFBIG, 'file exceeds bounded read limit', basename)",
|
|
214
|
+
" bytes_read = 0",
|
|
215
|
+
" while True:",
|
|
216
|
+
" read_size = 65536 if max_bytes is None else min(65536, max_bytes - bytes_read + 1)",
|
|
217
|
+
" chunk = os.read(file_fd, read_size)",
|
|
218
|
+
" if not chunk:",
|
|
219
|
+
" break",
|
|
220
|
+
" bytes_read += len(chunk)",
|
|
221
|
+
" if max_bytes is not None and bytes_read > max_bytes:",
|
|
222
|
+
" raise OSError(errno.EFBIG, 'file exceeds bounded read limit', basename)",
|
|
223
|
+
" write_all(1, chunk)",
|
|
224
|
+
" finally:",
|
|
225
|
+
" os.close(file_fd)",
|
|
226
|
+
"",
|
|
227
|
+
"def read_file(parent_fd, basename):",
|
|
228
|
+
" read_file_impl(parent_fd, basename, None)",
|
|
229
|
+
"",
|
|
230
|
+
"def read_file_bounded(parent_fd, basename, max_bytes):",
|
|
231
|
+
" if max_bytes < 0:",
|
|
232
|
+
" raise OSError(errno.EINVAL, 'read limit must be non-negative', basename)",
|
|
233
|
+
" read_file_impl(parent_fd, basename, max_bytes)",
|
|
234
|
+
"",
|
|
235
|
+
"def remove_tree(parent_fd, basename):",
|
|
236
|
+
" entry_stat = os.lstat(basename, dir_fd=parent_fd)",
|
|
237
|
+
" if not stat.S_ISDIR(entry_stat.st_mode) or stat.S_ISLNK(entry_stat.st_mode):",
|
|
238
|
+
" os.unlink(basename, dir_fd=parent_fd)",
|
|
239
|
+
" return",
|
|
240
|
+
" dir_fd = open_dir(basename, dir_fd=parent_fd)",
|
|
241
|
+
" try:",
|
|
242
|
+
" for child in os.listdir(dir_fd):",
|
|
243
|
+
" remove_tree(dir_fd, child)",
|
|
244
|
+
" finally:",
|
|
245
|
+
" os.close(dir_fd)",
|
|
246
|
+
" os.rmdir(basename, dir_fd=parent_fd)",
|
|
247
|
+
"",
|
|
248
|
+
"def entry_identity(entry_stat):",
|
|
249
|
+
" return (",
|
|
250
|
+
" entry_stat.st_dev,",
|
|
251
|
+
" entry_stat.st_ino,",
|
|
252
|
+
" entry_stat.st_mode,",
|
|
253
|
+
" entry_stat.st_size,",
|
|
254
|
+
" getattr(entry_stat, 'st_mtime_ns', int(entry_stat.st_mtime * 1000000000)),",
|
|
255
|
+
" getattr(entry_stat, 'st_ctime_ns', int(entry_stat.st_ctime * 1000000000)),",
|
|
256
|
+
" )",
|
|
257
|
+
"",
|
|
258
|
+
"def inode_identity(entry_stat):",
|
|
259
|
+
" return (entry_stat.st_dev, entry_stat.st_ino)",
|
|
260
|
+
"",
|
|
261
|
+
"def same_identity(expected, entry_stat):",
|
|
262
|
+
" return expected == entry_identity(entry_stat)",
|
|
263
|
+
"",
|
|
264
|
+
"def source_changed_error(basename):",
|
|
265
|
+
" return OSError(getattr(errno, 'ESTALE', errno.EIO), 'source changed during move fallback cleanup', basename)",
|
|
266
|
+
"",
|
|
267
|
+
"def copy_entry(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
|
|
268
|
+
" src_stat = os.lstat(src_basename, dir_fd=src_parent_fd)",
|
|
269
|
+
" if stat.S_ISDIR(src_stat.st_mode) and not stat.S_ISLNK(src_stat.st_mode):",
|
|
270
|
+
" os.mkdir(dst_basename, stat.S_IMODE(src_stat.st_mode) or 0o755, dir_fd=dst_parent_fd)",
|
|
271
|
+
" copied_children = []",
|
|
272
|
+
" src_dir_fd = None",
|
|
273
|
+
" dst_dir_fd = None",
|
|
274
|
+
" try:",
|
|
275
|
+
" src_dir_fd = open_dir(src_basename, dir_fd=src_parent_fd)",
|
|
276
|
+
" src_stat = os.fstat(src_dir_fd)",
|
|
277
|
+
" dst_dir_fd = open_dir(dst_basename, dir_fd=dst_parent_fd)",
|
|
278
|
+
" for child in os.listdir(src_dir_fd):",
|
|
279
|
+
" copied_children.append((child, copy_entry(src_dir_fd, child, dst_dir_fd, child)))",
|
|
280
|
+
" except Exception:",
|
|
281
|
+
" if dst_dir_fd is not None:",
|
|
282
|
+
" os.close(dst_dir_fd)",
|
|
283
|
+
" dst_dir_fd = None",
|
|
284
|
+
" try:",
|
|
285
|
+
" remove_tree(dst_parent_fd, dst_basename)",
|
|
286
|
+
" except FileNotFoundError:",
|
|
287
|
+
" pass",
|
|
288
|
+
" raise",
|
|
289
|
+
" finally:",
|
|
290
|
+
" if src_dir_fd is not None:",
|
|
291
|
+
" os.close(src_dir_fd)",
|
|
292
|
+
" if dst_dir_fd is not None:",
|
|
293
|
+
" os.close(dst_dir_fd)",
|
|
294
|
+
" return ('dir', entry_identity(src_stat), copied_children)",
|
|
295
|
+
" if stat.S_ISLNK(src_stat.st_mode):",
|
|
296
|
+
" link_target = os.readlink(src_basename, dir_fd=src_parent_fd)",
|
|
297
|
+
" os.symlink(link_target, dst_basename, dir_fd=dst_parent_fd)",
|
|
298
|
+
" return ('leaf', entry_identity(src_stat), None)",
|
|
299
|
+
" return ('leaf', copy_regular_file(src_parent_fd, src_basename, dst_parent_fd, dst_basename), None)",
|
|
300
|
+
"",
|
|
301
|
+
"def remove_copied_entry(parent_fd, basename, manifest):",
|
|
302
|
+
" kind, expected_identity, children = manifest",
|
|
303
|
+
" current_stat = os.lstat(basename, dir_fd=parent_fd)",
|
|
304
|
+
" if not same_identity(expected_identity, current_stat):",
|
|
305
|
+
" raise source_changed_error(basename)",
|
|
306
|
+
" if kind != 'dir':",
|
|
307
|
+
" os.unlink(basename, dir_fd=parent_fd)",
|
|
308
|
+
" return",
|
|
309
|
+
" dir_fd = open_dir(basename, dir_fd=parent_fd)",
|
|
310
|
+
" try:",
|
|
311
|
+
" for child, child_manifest in children:",
|
|
312
|
+
" remove_copied_entry(dir_fd, child, child_manifest)",
|
|
313
|
+
" finally:",
|
|
314
|
+
" os.close(dir_fd)",
|
|
315
|
+
" os.rmdir(basename, dir_fd=parent_fd)",
|
|
316
|
+
"",
|
|
317
|
+
"def move_entry(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
|
|
318
|
+
" try:",
|
|
319
|
+
" os.rename(src_basename, dst_basename, src_dir_fd=src_parent_fd, dst_dir_fd=dst_parent_fd)",
|
|
320
|
+
" os.fsync(dst_parent_fd)",
|
|
321
|
+
" os.fsync(src_parent_fd)",
|
|
322
|
+
" return",
|
|
323
|
+
" except OSError as err:",
|
|
324
|
+
" if err.errno != errno.EXDEV:",
|
|
325
|
+
" raise",
|
|
326
|
+
" src_stat = os.lstat(src_basename, dir_fd=src_parent_fd)",
|
|
327
|
+
" if stat.S_ISDIR(src_stat.st_mode) and not stat.S_ISLNK(src_stat.st_mode):",
|
|
328
|
+
" temp_dir_name = create_temp_dir(dst_parent_fd, stat.S_IMODE(src_stat.st_mode) or 0o755)",
|
|
329
|
+
" copied_children = []",
|
|
330
|
+
" temp_dir_fd = None",
|
|
331
|
+
" src_dir_fd = None",
|
|
332
|
+
" try:",
|
|
333
|
+
" temp_dir_fd = open_dir(temp_dir_name, dir_fd=dst_parent_fd)",
|
|
334
|
+
" src_dir_fd = open_dir(src_basename, dir_fd=src_parent_fd)",
|
|
335
|
+
" src_stat = os.fstat(src_dir_fd)",
|
|
336
|
+
" for child in os.listdir(src_dir_fd):",
|
|
337
|
+
" copied_children.append((child, copy_entry(src_dir_fd, child, temp_dir_fd, child)))",
|
|
338
|
+
" os.close(src_dir_fd)",
|
|
339
|
+
" src_dir_fd = None",
|
|
340
|
+
" os.close(temp_dir_fd)",
|
|
341
|
+
" temp_dir_fd = None",
|
|
342
|
+
" os.rename(temp_dir_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
|
|
343
|
+
" except Exception:",
|
|
344
|
+
" if src_dir_fd is not None:",
|
|
345
|
+
" os.close(src_dir_fd)",
|
|
346
|
+
" if temp_dir_fd is not None:",
|
|
347
|
+
" os.close(temp_dir_fd)",
|
|
348
|
+
" try:",
|
|
349
|
+
" remove_tree(dst_parent_fd, temp_dir_name)",
|
|
350
|
+
" except FileNotFoundError:",
|
|
351
|
+
" pass",
|
|
352
|
+
" raise",
|
|
353
|
+
" remove_copied_entry(src_parent_fd, src_basename, ('dir', entry_identity(src_stat), copied_children))",
|
|
354
|
+
" os.fsync(dst_parent_fd)",
|
|
355
|
+
" os.fsync(src_parent_fd)",
|
|
356
|
+
" return",
|
|
357
|
+
" if stat.S_ISLNK(src_stat.st_mode):",
|
|
358
|
+
" link_target = os.readlink(src_basename, dir_fd=src_parent_fd)",
|
|
359
|
+
" try:",
|
|
360
|
+
" os.unlink(dst_basename, dir_fd=dst_parent_fd)",
|
|
361
|
+
" except FileNotFoundError:",
|
|
362
|
+
" pass",
|
|
363
|
+
" os.symlink(link_target, dst_basename, dir_fd=dst_parent_fd)",
|
|
364
|
+
" os.unlink(src_basename, dir_fd=src_parent_fd)",
|
|
365
|
+
" os.fsync(dst_parent_fd)",
|
|
366
|
+
" os.fsync(src_parent_fd)",
|
|
367
|
+
" return",
|
|
368
|
+
" src_fd = os.open(src_basename, READ_FLAGS, dir_fd=src_parent_fd)",
|
|
369
|
+
" temp_fd = None",
|
|
370
|
+
" temp_name = None",
|
|
371
|
+
" try:",
|
|
372
|
+
" src_file_stat = os.fstat(src_fd)",
|
|
373
|
+
" if not stat.S_ISREG(src_file_stat.st_mode):",
|
|
374
|
+
" raise OSError(errno.EPERM, 'only regular files are allowed', src_basename)",
|
|
375
|
+
" if src_file_stat.st_nlink > 1:",
|
|
376
|
+
" raise OSError(errno.EPERM, 'hardlinked file is not allowed', src_basename)",
|
|
377
|
+
" temp_name, temp_fd = create_temp_file(dst_parent_fd)",
|
|
378
|
+
" while True:",
|
|
379
|
+
" chunk = os.read(src_fd, 65536)",
|
|
380
|
+
" if not chunk:",
|
|
381
|
+
" break",
|
|
382
|
+
" write_all(temp_fd, chunk)",
|
|
383
|
+
" try:",
|
|
384
|
+
" os.fchmod(temp_fd, stat.S_IMODE(src_stat.st_mode))",
|
|
385
|
+
" except AttributeError:",
|
|
386
|
+
" pass",
|
|
387
|
+
" os.fsync(temp_fd)",
|
|
388
|
+
" os.close(temp_fd)",
|
|
389
|
+
" temp_fd = None",
|
|
390
|
+
" os.replace(temp_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
|
|
391
|
+
" temp_name = None",
|
|
392
|
+
" os.unlink(src_basename, dir_fd=src_parent_fd)",
|
|
393
|
+
" os.fsync(dst_parent_fd)",
|
|
394
|
+
" os.fsync(src_parent_fd)",
|
|
395
|
+
" finally:",
|
|
396
|
+
" if temp_fd is not None:",
|
|
397
|
+
" os.close(temp_fd)",
|
|
398
|
+
" if temp_name is not None:",
|
|
399
|
+
" try:",
|
|
400
|
+
" os.unlink(temp_name, dir_fd=dst_parent_fd)",
|
|
401
|
+
" except FileNotFoundError:",
|
|
402
|
+
" pass",
|
|
403
|
+
" os.close(src_fd)",
|
|
404
|
+
"",
|
|
405
|
+
"basename_indexes = (4, 7) if operation in ('copy', 'rename') else (4,) if operation in ('read', 'write', 'create', 'remove') else ()",
|
|
406
|
+
"for basename_index in basename_indexes:",
|
|
407
|
+
" basename = sys.argv[basename_index]",
|
|
408
|
+
" if not basename or basename in ('.', '..') or '/' in basename or '\\x00' in basename:",
|
|
409
|
+
" raise OSError(errno.EINVAL, 'invalid basename', basename)",
|
|
410
|
+
"",
|
|
411
|
+
GUEST_FILESYSTEM_DISPATCH_PYTHON,
|
|
412
|
+
].join("\n");
|
|
413
|
+
export { GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON };
|
package/dist/home-dir.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"AAoBA,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAGpB;AAwCD,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAER;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAYR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAaR"}
|
package/dist/home-dir.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { normalizeOptionalString } from "./string-coerce.js";
|
|
4
|
+
function hasHomePrefix(input) {
|
|
5
|
+
return input === "~" || input.startsWith("~/") ||
|
|
6
|
+
(path.sep === "\\" && input.startsWith("~\\"));
|
|
7
|
+
}
|
|
4
8
|
function normalize(value) {
|
|
5
9
|
const trimmed = normalizeOptionalString(value);
|
|
6
10
|
if (!trimmed) {
|
|
@@ -20,8 +24,7 @@ function resolveRawHomeDir(env, homedir) {
|
|
|
20
24
|
if (!explicitHome) {
|
|
21
25
|
return resolveRawOsHomeDir(env, homedir);
|
|
22
26
|
}
|
|
23
|
-
|
|
24
|
-
if (segments[0] !== "~") {
|
|
27
|
+
if (!hasHomePrefix(explicitHome)) {
|
|
25
28
|
return explicitHome;
|
|
26
29
|
}
|
|
27
30
|
// OPENCLAW_HOME starts with "~"; expand against the os home dir. Fall
|
|
@@ -56,8 +59,7 @@ export function resolveRequiredHomeDir(env = process.env, homedir = os.homedir)
|
|
|
56
59
|
return resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
|
|
57
60
|
}
|
|
58
61
|
export function expandHomePrefix(input, opts) {
|
|
59
|
-
|
|
60
|
-
if (segments[0] !== "~") {
|
|
62
|
+
if (!hasHomePrefix(input)) {
|
|
61
63
|
return input;
|
|
62
64
|
}
|
|
63
65
|
const home = normalize(opts?.home) ??
|
|
@@ -65,14 +67,14 @@ export function expandHomePrefix(input, opts) {
|
|
|
65
67
|
if (!home) {
|
|
66
68
|
return input;
|
|
67
69
|
}
|
|
68
|
-
|
|
70
|
+
// Expand before normalizing so a following .. traverses the actual home.
|
|
71
|
+
return path.join(home, input.slice(2));
|
|
69
72
|
}
|
|
70
73
|
export function resolveHomeRelativePath(input, opts) {
|
|
71
74
|
if (!input) {
|
|
72
75
|
return input;
|
|
73
76
|
}
|
|
74
|
-
|
|
75
|
-
if (segments[0] !== "~") {
|
|
77
|
+
if (!hasHomePrefix(input)) {
|
|
76
78
|
return path.resolve(input);
|
|
77
79
|
}
|
|
78
80
|
const expanded = expandHomePrefix(input, {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { FsSafeError, categorizeFsSafeError, type FsSafeErrorCategory, type FsSafeErrorCode, type FsSafeErrorDetails, } from "./errors.js";
|
|
2
|
-
export { DEFAULT_ROOT_MAX_BYTES, root, type DenyMutationPolicy, type HardlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCreateJsonOptions, type RootCreateOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type RootWalkDataEntry, type RootWalkDataEntryKind, type RootWalkDirectoryErrorBehavior, type RootWalkEntry, type RootWalkEntryFilter, type RootWalkEntryFilterResult, type RootWalkEntryKind, type RootWalkLimitBehavior, type RootWalkOptions, type RootWalkSymlinkPolicy, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root.js";
|
|
2
|
+
export { DEFAULT_ROOT_MAX_BYTES, root, type DenyMutationPolicy, type HardlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCopySource, type RootCopyPublicationReceipt, type CopyCloneMode, type RootCreateJsonOptions, type RootCreateOptions, type RootCreateStreamOptions, type RootDefaults, type RootEntriesOptions, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type RootWalkDataEntry, type RootWalkDataEntryKind, type RootWalkDirectoryErrorBehavior, type RootWalkEntry, type RootWalkEntryFilter, type RootWalkEntryFilterResult, type RootWalkEntryKind, type RootWalkLimitBehavior, type RootWalkOptions, type RootWalkSymlinkPolicy, type SymlinkPolicy, type MutationSymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root.js";
|
|
3
3
|
export type { ContainmentGuarantee } from "./containment.js";
|
|
4
4
|
export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, type FsSafePythonConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
|
|
5
5
|
export { writeExternalFileWithinRoot, type ExternalFileWriteOptions, type ExternalFileWriteResult, } from "./output.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,2BAA2B,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,0BAA0B,EAC/B,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,2BAA2B,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB3D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAe5D;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDhB"}
|
package/dist/install-path.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import fsSync from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { isPathInside } from "./path.js";
|
|
5
|
+
import { realpathSync } from "./realpath.js";
|
|
5
6
|
export function safeDirName(input) {
|
|
6
7
|
const trimmed = input.trim();
|
|
7
8
|
if (!trimmed) {
|
|
@@ -19,12 +20,8 @@ export function safePathSegmentHashed(input) {
|
|
|
19
20
|
.replaceAll(/-+$/g, "");
|
|
20
21
|
const normalized = base.length > 0 ? base : "skill";
|
|
21
22
|
const safe = normalized === "." || normalized === ".." ? "skill" : normalized;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const prefix = safe.length > 50 ? safe.slice(0, 50) : safe;
|
|
25
|
-
return `${prefix}-${hash}`;
|
|
26
|
-
}
|
|
27
|
-
if (safe.length > 60) {
|
|
23
|
+
if (safe !== trimmed || safe.length > 60) {
|
|
24
|
+
const hash = createHash("sha256").update(trimmed).digest("hex").slice(0, 10);
|
|
28
25
|
return `${safe.slice(0, 50)}-${hash}`;
|
|
29
26
|
}
|
|
30
27
|
return safe;
|
|
@@ -59,7 +56,7 @@ export async function assertCanonicalPathWithinBase(params) {
|
|
|
59
56
|
else if (!baseLstat.isDirectory()) {
|
|
60
57
|
throw new Error(`Invalid ${params.boundaryLabel}: base directory must be a directory`);
|
|
61
58
|
}
|
|
62
|
-
const baseRealPath =
|
|
59
|
+
const baseRealPath = realpathSync.native(baseDir);
|
|
63
60
|
const validateDirectory = async (dirPath) => {
|
|
64
61
|
const resolvedDirPath = path.resolve(dirPath);
|
|
65
62
|
const dirLstat = fsSync.lstatSync(dirPath);
|
|
@@ -75,7 +72,7 @@ export async function assertCanonicalPathWithinBase(params) {
|
|
|
75
72
|
else if (!dirLstat.isDirectory()) {
|
|
76
73
|
throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
|
|
77
74
|
}
|
|
78
|
-
const dirRealPath =
|
|
75
|
+
const dirRealPath = realpathSync.native(dirPath);
|
|
79
76
|
if (!isPathInside(baseRealPath, dirRealPath)) {
|
|
80
77
|
throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
|
|
81
78
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import fsSync from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { syncDirectory } from "./directory-durability.js";
|
|
4
3
|
import { isPathRelativeEscape } from "./path.js";
|
|
4
|
+
import { realpathSync } from "./realpath.js";
|
|
5
5
|
export async function syncQueueDirectoryCreation(dir, validationBase) {
|
|
6
|
-
const baseReal =
|
|
7
|
-
const targetReal =
|
|
6
|
+
const baseReal = realpathSync.native(validationBase);
|
|
7
|
+
const targetReal = realpathSync.native(dir);
|
|
8
8
|
const relative = path.relative(baseReal, targetReal);
|
|
9
9
|
if (isPathRelativeEscape(relative)) {
|
|
10
10
|
throw new Error(`durable queue directory escapes validation base: ${dir}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAOF,eAAO,MAAM,0CAA0C,QAAmB,CAAC;AAM3E,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAO5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAiLD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAyDD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CASZ;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/F;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE;IACzD,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAsBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CA+Dd;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import fs, {} from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
4
|
+
import { readBoundedAsync } from "./bounded-read.js";
|
|
4
5
|
import { syncDirectory } from "./directory-durability.js";
|
|
5
6
|
import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
|
|
6
7
|
import { getErrorCode, acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
|
|
7
8
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
8
9
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
10
|
+
import { realpathSync } from "./realpath.js";
|
|
11
|
+
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
9
12
|
import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
|
|
10
13
|
import { assertSafePathSegment } from "./safe-path-segment.js";
|
|
11
14
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
@@ -59,7 +62,7 @@ async function ensureJsonDurableQueueDir(dir, validationRoot) {
|
|
|
59
62
|
? validationRoot
|
|
60
63
|
: queueValidationRoot(dir);
|
|
61
64
|
await assertNoSymlinkDirectorySegments(root, dir, true);
|
|
62
|
-
await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
65
|
+
await fs.promises.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: 0o700 });
|
|
63
66
|
await assertNoSymlinkDirectorySegments(root, dir, false);
|
|
64
67
|
await chmodQueueDirectory(dir);
|
|
65
68
|
await syncQueueDirectoryCreation(dir, root.path);
|
|
@@ -114,7 +117,7 @@ async function isDarwinSystemAlias(dir, stat) {
|
|
|
114
117
|
return false;
|
|
115
118
|
}
|
|
116
119
|
try {
|
|
117
|
-
return
|
|
120
|
+
return realpathSync.native(resolved) === `/private${resolved}`;
|
|
118
121
|
}
|
|
119
122
|
catch {
|
|
120
123
|
return false;
|
|
@@ -130,7 +133,7 @@ async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissin
|
|
|
130
133
|
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
131
134
|
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
132
135
|
}
|
|
133
|
-
base =
|
|
136
|
+
base = realpathSync.native(base);
|
|
134
137
|
target = path.join(base, ...relative.split(path.sep).filter(Boolean));
|
|
135
138
|
current = base;
|
|
136
139
|
baseStat = fs.lstatSync(base);
|
|
@@ -158,7 +161,7 @@ async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissin
|
|
|
158
161
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
159
162
|
if (stat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
|
|
160
163
|
if (await isDarwinSystemAlias(current, stat)) {
|
|
161
|
-
current =
|
|
164
|
+
current = realpathSync.native(current);
|
|
162
165
|
continue;
|
|
163
166
|
}
|
|
164
167
|
}
|
|
@@ -253,20 +256,12 @@ async function readBoundedUtf8File(params) {
|
|
|
253
256
|
try {
|
|
254
257
|
const openedStat = await inspectQueueEntry(() => fs.fstatSync(handle.fd, { bigint: true }), params.maxBytes, initialStat);
|
|
255
258
|
await inspectQueueEntry(inspectPath, params.maxBytes, openedStat);
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return Buffer.concat(chunks, total).toString("utf8");
|
|
263
|
-
}
|
|
264
|
-
total += bytesRead;
|
|
265
|
-
if (total > params.maxBytes) {
|
|
266
|
-
throw new Error(`queue entry exceeds ${params.maxBytes} bytes`);
|
|
267
|
-
}
|
|
268
|
-
chunks.push(Buffer.from(scratch.subarray(0, bytesRead)));
|
|
269
|
-
}
|
|
259
|
+
const bytes = await readBoundedAsync(params.maxBytes, async (buffer, length) => (await handle.read(buffer, 0, length, null)).bytesRead, {
|
|
260
|
+
initialSize: openedStat.size >= 0n && Number.isSafeInteger(Number(openedStat.size))
|
|
261
|
+
? Number(openedStat.size) : undefined,
|
|
262
|
+
createLimitError: () => new Error(`queue entry exceeds ${params.maxBytes} bytes`),
|
|
263
|
+
});
|
|
264
|
+
return bytes.toString("utf8");
|
|
270
265
|
}
|
|
271
266
|
finally {
|
|
272
267
|
await handle.close();
|
|
@@ -323,6 +318,12 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
323
318
|
for (const file of files) {
|
|
324
319
|
if (file.endsWith(".delivered")) {
|
|
325
320
|
const id = file.slice(0, -".delivered".length);
|
|
321
|
+
try {
|
|
322
|
+
assertSafeQueueEntryId(id);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
326
327
|
await completeDeliveredQueueEntry(resolveJsonDurableQueueEntryPaths(options.queueDir, id));
|
|
327
328
|
}
|
|
328
329
|
else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {
|