@openclaw/fs-safe 0.4.7 → 0.5.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 +45 -0
- package/README.md +82 -43
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +5 -4
- package/dist/archive-errors.d.ts +11 -0
- package/dist/archive-errors.d.ts.map +1 -0
- package/dist/archive-errors.js +16 -0
- package/dist/archive-input.d.ts +12 -0
- package/dist/archive-input.d.ts.map +1 -0
- package/dist/archive-input.js +72 -0
- package/dist/archive-kind.d.ts +1 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +18 -0
- package/dist/archive-limits.d.ts +10 -0
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +18 -0
- package/dist/archive-native.d.ts +18 -0
- package/dist/archive-native.d.ts.map +1 -0
- package/dist/archive-native.js +115 -0
- package/dist/archive-options.d.ts +21 -0
- package/dist/archive-options.d.ts.map +1 -0
- package/dist/archive-options.js +1 -0
- package/dist/archive-policy.d.ts +20 -0
- package/dist/archive-policy.d.ts.map +1 -0
- package/dist/archive-policy.js +29 -0
- package/dist/archive-read.d.ts +6 -0
- package/dist/archive-read.d.ts.map +1 -0
- package/dist/archive-read.js +200 -0
- package/dist/archive-staging.d.ts +2 -5
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +19 -10
- package/dist/archive-tar-meta.d.ts +6 -0
- package/dist/archive-tar-meta.d.ts.map +1 -0
- package/dist/archive-tar-meta.js +143 -0
- package/dist/archive-tar-runtime.d.ts +43 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -0
- package/dist/archive-tar-runtime.js +8 -0
- package/dist/archive-tar.d.ts +5 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +24 -6
- package/dist/archive-zip-entry.d.ts +15 -0
- package/dist/archive-zip-entry.d.ts.map +1 -0
- package/dist/archive-zip-entry.js +17 -0
- package/dist/archive.d.ts +7 -17
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +111 -108
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/containment.d.ts +2 -0
- package/dist/containment.d.ts.map +1 -0
- package/dist/containment.js +1 -0
- package/dist/durability.d.ts +2 -0
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +2 -0
- package/dist/errors.d.ts +4 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -0
- package/dist/file-hash.d.ts +10 -0
- package/dist/file-hash.d.ts.map +1 -0
- package/dist/file-hash.js +73 -0
- package/dist/file-lock-sync.d.ts +35 -0
- package/dist/file-lock-sync.d.ts.map +1 -0
- package/dist/file-lock-sync.js +233 -0
- package/dist/file-lock.d.ts +2 -0
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +4 -1
- package/dist/guarded-mkdir.d.ts +7 -1
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +45 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +30 -15
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +101 -17
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
- package/dist/native-binding.d.ts +79 -0
- package/dist/native-binding.d.ts.map +1 -0
- package/dist/native-binding.js +1 -0
- package/dist/native-config.d.ts +17 -0
- package/dist/native-config.d.ts.map +1 -0
- package/dist/native-config.js +71 -0
- package/dist/native-operations.d.ts +21 -0
- package/dist/native-operations.d.ts.map +1 -0
- package/dist/native-operations.js +115 -0
- package/dist/native-pinned-write.d.ts +5 -0
- package/dist/native-pinned-write.d.ts.map +1 -0
- package/dist/native-pinned-write.js +115 -0
- package/dist/native.d.ts +14 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +204 -0
- package/dist/output-sibling.d.ts +8 -0
- package/dist/output-sibling.d.ts.map +1 -0
- package/dist/output-sibling.js +122 -0
- package/dist/output.d.ts +2 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +32 -5
- package/dist/owner-dacl.d.ts +31 -0
- package/dist/owner-dacl.d.ts.map +1 -0
- package/dist/owner-dacl.js +27 -0
- package/dist/path-policy.js +1 -1
- package/dist/permissions-public.d.ts +2 -0
- package/dist/permissions-public.d.ts.map +1 -1
- package/dist/permissions-public.js +2 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +6 -0
- package/dist/pinned-operation.d.ts +2 -0
- package/dist/pinned-operation.d.ts.map +1 -0
- package/dist/pinned-operation.js +35 -0
- package/dist/pinned-write.d.ts +3 -16
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +20 -85
- package/dist/private-directory.d.ts +5 -0
- package/dist/private-directory.d.ts.map +1 -0
- package/dist/private-directory.js +13 -0
- package/dist/private-temp-workspace.d.ts +6 -2
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +40 -13
- package/dist/publish-file-failure.d.ts +27 -0
- package/dist/publish-file-failure.d.ts.map +1 -0
- package/dist/publish-file-failure.js +21 -0
- package/dist/publish-file.d.ts +21 -0
- package/dist/publish-file.d.ts.map +1 -0
- package/dist/publish-file.js +351 -0
- package/dist/read-opened-file.d.ts +3 -0
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +1 -0
- package/dist/replace-file-copy-fallback.d.ts +34 -0
- package/dist/replace-file-copy-fallback.d.ts.map +1 -0
- package/dist/replace-file-copy-fallback.js +339 -0
- package/dist/replace-file.d.ts +6 -2
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +39 -90
- package/dist/root-context.d.ts +4 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +20 -1
- package/dist/root-impl.d.ts +5 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +128 -225
- package/dist/root-path-existing.d.ts +3 -0
- package/dist/root-path-existing.d.ts.map +1 -0
- package/dist/root-path-existing.js +69 -0
- package/dist/root-path.d.ts +1 -2
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +67 -81
- package/dist/root-walk.d.ts +41 -0
- package/dist/root-walk.d.ts.map +1 -0
- package/dist/root-walk.js +114 -0
- package/dist/root.d.ts +2 -0
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts +5 -2
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +20 -2
- package/dist/secret-read-async.d.ts +4 -0
- package/dist/secret-read-async.d.ts.map +1 -0
- package/dist/secret-read-async.js +95 -0
- package/dist/secret.d.ts +2 -1
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +2 -1
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +1 -0
- package/dist/sidecar-lock-handle.d.ts +20 -0
- package/dist/sidecar-lock-handle.d.ts.map +1 -0
- package/dist/sidecar-lock-handle.js +28 -0
- package/dist/sidecar-lock-policy.d.ts +10 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -0
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts +22 -6
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +106 -20
- package/dist/sidecar-lock-types.d.ts +56 -0
- package/dist/sidecar-lock-types.d.ts.map +1 -0
- package/dist/sidecar-lock-types.js +1 -0
- package/dist/sidecar-lock.d.ts +2 -45
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +94 -55
- package/dist/temp-cleanup.d.ts +7 -1
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +35 -5
- package/dist/temp.d.ts +2 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +3 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/windows-permissions-native.d.ts +9 -0
- package/dist/windows-permissions-native.d.ts.map +1 -0
- package/dist/windows-permissions-native.js +31 -0
- package/docs/archive.md +131 -13
- package/docs/atomic.md +30 -1
- package/docs/config.md +51 -21
- package/docs/durability.md +211 -1
- package/docs/errors.md +36 -6
- package/docs/filename.md +0 -0
- package/docs/index.md +11 -8
- package/docs/install.md +27 -26
- package/docs/json-store.md +18 -3
- package/docs/migrating-to-0.5.md +196 -0
- package/docs/native-helper.md +85 -0
- package/docs/native.md +134 -0
- package/docs/output.md +37 -11
- package/docs/path.md +1 -1
- package/docs/permissions.md +84 -1
- package/docs/quickstart.md +5 -2
- package/docs/reading.md +4 -4
- package/docs/root.md +31 -15
- package/docs/secret-file.md +50 -1
- package/docs/security-model.md +29 -8
- package/docs/sidecar-lock.md +92 -7
- package/docs/temp.md +24 -1
- package/docs/test-hooks.md +22 -2
- package/docs/testing.md +18 -7
- package/docs/types.md +3 -1
- package/docs/walk.md +55 -0
- package/docs/writing.md +3 -3
- package/package.json +8 -2
- package/dist/pinned-helper.d.ts +0 -9
- package/dist/pinned-helper.d.ts.map +0 -1
- package/dist/pinned-helper.js +0 -19
- package/dist/pinned-path.d.ts +0 -7
- package/dist/pinned-path.d.ts.map +0 -1
- package/dist/pinned-path.js +0 -21
- package/dist/pinned-python-config.d.ts +0 -9
- package/dist/pinned-python-config.d.ts.map +0 -1
- package/dist/pinned-python-config.js +0 -38
- package/dist/pinned-python.d.ts +0 -12
- package/dist/pinned-python.d.ts.map +0 -1
- package/dist/pinned-python.js +0 -688
- package/docs/python-helper.md +0 -103
package/dist/archive.js
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import { constants as fsConstants } from "node:fs";
|
|
1
|
+
import fsSync, { constants as fsConstants } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Readable } from "node:stream";
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
6
|
import { resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
7
7
|
import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
|
|
8
|
-
import {
|
|
9
|
-
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, } from "./archive-limits.js";
|
|
8
|
+
import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, } from "./archive-limits.js";
|
|
10
9
|
import { resolveArchiveKind } from "./archive-kind.js";
|
|
11
10
|
import { mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
12
11
|
import { createTarEntryPreflightChecker, readTarEntryInfo, } from "./archive-tar.js";
|
|
13
12
|
import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
14
|
-
import {
|
|
13
|
+
import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
|
|
14
|
+
import { FsSafeError } from "./errors.js";
|
|
15
|
+
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
16
|
+
import { extractNativeArchive } from "./archive-native.js";
|
|
17
|
+
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
18
|
+
import { getNativeBinding } from "./native.js";
|
|
19
|
+
import { resolveArchiveEntryMode, shouldExtractArchiveEntry, } from "./archive-policy.js";
|
|
20
|
+
import { importOptionalTar } from "./archive-tar-runtime.js";
|
|
21
|
+
import { preflightTarMetadata } from "./archive-tar-meta.js";
|
|
15
22
|
import { writeSiblingTempFile } from "./sibling-temp.js";
|
|
16
|
-
import { tempFile } from "./temp-target.js";
|
|
17
23
|
export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
18
24
|
export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
|
|
19
|
-
export {
|
|
25
|
+
export { readArchiveEntry } from "./archive-read.js";
|
|
26
|
+
export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, } from "./archive-limits.js";
|
|
27
|
+
export { ArchiveFormatError } from "./archive-errors.js";
|
|
20
28
|
export { ArchiveSecurityError } from "./archive-staging.js";
|
|
21
29
|
export { createArchiveSymlinkTraversalError, mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
22
30
|
export { createTarEntryPreflightChecker } from "./archive-tar.js";
|
|
@@ -26,91 +34,6 @@ const OPEN_WRITE_CREATE_FLAGS = fsConstants.O_WRONLY |
|
|
|
26
34
|
fsConstants.O_CREAT |
|
|
27
35
|
fsConstants.O_EXCL |
|
|
28
36
|
(SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0);
|
|
29
|
-
const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
|
|
30
|
-
const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
|
|
31
|
-
function isZipSymlinkEntry(entry) {
|
|
32
|
-
return (typeof entry.unixPermissions === "number" &&
|
|
33
|
-
(entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE);
|
|
34
|
-
}
|
|
35
|
-
function zipEntryFileMode(entry) {
|
|
36
|
-
if (typeof entry.unixPermissions !== "number") {
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
39
|
-
const mode = entry.unixPermissions & 0o777;
|
|
40
|
-
return mode === 0 ? undefined : mode;
|
|
41
|
-
}
|
|
42
|
-
async function cleanupStagedArchiveFile(staged) {
|
|
43
|
-
if (staged) {
|
|
44
|
-
await staged.cleanup().catch(() => undefined);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
async function closeFileHandle(handle) {
|
|
48
|
-
if (handle) {
|
|
49
|
-
await handle.close().catch(() => undefined);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async function stageArchiveFileForExtraction(params) {
|
|
53
|
-
params.deadline.check();
|
|
54
|
-
const sourcePath = path.resolve(params.archivePath);
|
|
55
|
-
const initialStat = await fs.lstat(sourcePath);
|
|
56
|
-
if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
|
|
57
|
-
throw new Error(`archive is not a regular file: ${params.archivePath}`);
|
|
58
|
-
}
|
|
59
|
-
if (initialStat.size > params.limits.maxArchiveBytes) {
|
|
60
|
-
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
|
|
61
|
-
}
|
|
62
|
-
const noFollow = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants ? fsConstants.O_NOFOLLOW : 0;
|
|
63
|
-
const handle = await fs.open(sourcePath, fsConstants.O_RDONLY | noFollow);
|
|
64
|
-
let staged;
|
|
65
|
-
let output;
|
|
66
|
-
try {
|
|
67
|
-
staged = await tempFile({
|
|
68
|
-
prefix: "fs-safe-archive-input",
|
|
69
|
-
fileName: path.basename(sourcePath),
|
|
70
|
-
});
|
|
71
|
-
const openedStat = await handle.stat();
|
|
72
|
-
const pathStat = await fs.lstat(sourcePath);
|
|
73
|
-
if (!openedStat.isFile() ||
|
|
74
|
-
pathStat.isSymbolicLink() ||
|
|
75
|
-
!pathStat.isFile() ||
|
|
76
|
-
!sameFileIdentity(initialStat, openedStat) ||
|
|
77
|
-
!sameFileIdentity(pathStat, openedStat)) {
|
|
78
|
-
throw new Error("archive changed during validation");
|
|
79
|
-
}
|
|
80
|
-
output = await fs.open(staged.path, OPEN_WRITE_CREATE_FLAGS, 0o600);
|
|
81
|
-
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
82
|
-
let written = 0;
|
|
83
|
-
while (true) {
|
|
84
|
-
params.deadline.check();
|
|
85
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
86
|
-
if (bytesRead === 0) {
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
written += bytesRead;
|
|
90
|
-
if (written > params.limits.maxArchiveBytes) {
|
|
91
|
-
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
|
|
92
|
-
}
|
|
93
|
-
await writeFileHandleFully({
|
|
94
|
-
handle: output,
|
|
95
|
-
buffer,
|
|
96
|
-
bytes: bytesRead,
|
|
97
|
-
deadline: params.deadline,
|
|
98
|
-
});
|
|
99
|
-
params.deadline.check();
|
|
100
|
-
}
|
|
101
|
-
await output.close();
|
|
102
|
-
output = undefined;
|
|
103
|
-
return staged;
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
await closeFileHandle(output);
|
|
107
|
-
await cleanupStagedArchiveFile(staged);
|
|
108
|
-
throw err;
|
|
109
|
-
}
|
|
110
|
-
finally {
|
|
111
|
-
await closeFileHandle(handle);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
37
|
async function readZipEntryStream(entry) {
|
|
115
38
|
if (typeof entry.nodeStream === "function") {
|
|
116
39
|
return entry.nodeStream();
|
|
@@ -150,7 +73,7 @@ async function writeZipFileEntry(params) {
|
|
|
150
73
|
dir: path.dirname(destinationPath),
|
|
151
74
|
tempPrefix: `.${path.basename(destinationPath)}.fs-safe-archive`,
|
|
152
75
|
chmodDir: false,
|
|
153
|
-
mode:
|
|
76
|
+
mode: params.mode,
|
|
154
77
|
writeTemp: async (tempPath) => {
|
|
155
78
|
tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o666);
|
|
156
79
|
const writable = tempHandle.createWriteStream();
|
|
@@ -218,6 +141,18 @@ async function extractZip(params) {
|
|
|
218
141
|
if (!output) {
|
|
219
142
|
continue;
|
|
220
143
|
}
|
|
144
|
+
assertArchiveEntryPathComponentsWithinLimit(output.relPath, limits);
|
|
145
|
+
const isSymlink = isZipSymlinkEntry(entry);
|
|
146
|
+
const entryKind = isSymlink ? "symlink" : entry.dir ? "directory" : "file";
|
|
147
|
+
const entrySize = zipEntryDeclaredSize(entry);
|
|
148
|
+
if (!shouldExtractArchiveEntry({
|
|
149
|
+
filter: params.entryFilter,
|
|
150
|
+
onFiltered: params.onFiltered,
|
|
151
|
+
entry: { path: entry.name, kind: entryKind, size: entrySize },
|
|
152
|
+
})) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const mode = zipEntryMode(entry, params.entryModes);
|
|
221
156
|
await prepareZipOutputPath({
|
|
222
157
|
destinationDir: stagingRealDir,
|
|
223
158
|
destinationRealDir: stagingRealDir,
|
|
@@ -227,16 +162,18 @@ async function extractZip(params) {
|
|
|
227
162
|
isDirectory: entry.dir,
|
|
228
163
|
});
|
|
229
164
|
if (entry.dir) {
|
|
165
|
+
await fs.chmod(output.outPath, mode);
|
|
230
166
|
continue;
|
|
231
167
|
}
|
|
232
|
-
if (
|
|
233
|
-
throw new
|
|
168
|
+
if (isSymlink) {
|
|
169
|
+
throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${entry.name}`);
|
|
234
170
|
}
|
|
235
171
|
await writeZipFileEntry({
|
|
236
172
|
entry,
|
|
237
173
|
outPath: output.outPath,
|
|
238
174
|
budget,
|
|
239
175
|
deadline: params.deadline,
|
|
176
|
+
mode,
|
|
240
177
|
});
|
|
241
178
|
}
|
|
242
179
|
params.deadline.check();
|
|
@@ -259,6 +196,25 @@ export async function extractArchive(params) {
|
|
|
259
196
|
throw new Error(`unsupported archive: ${params.archivePath}`);
|
|
260
197
|
}
|
|
261
198
|
const label = kind === "zip" ? "extract zip" : "extract tar";
|
|
199
|
+
const native = getNativeBinding();
|
|
200
|
+
if (native) {
|
|
201
|
+
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => extractNativeArchive({
|
|
202
|
+
binding: native,
|
|
203
|
+
archivePath: params.archivePath,
|
|
204
|
+
destDir: params.destDir,
|
|
205
|
+
kind,
|
|
206
|
+
stripComponents: params.stripComponents,
|
|
207
|
+
limits: params.limits,
|
|
208
|
+
deadline,
|
|
209
|
+
entryModes: params.entryModes,
|
|
210
|
+
entryFilter: params.entryFilter,
|
|
211
|
+
onFiltered: params.onFiltered,
|
|
212
|
+
}));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (kind === "tar-zstd" || kind === "tar-bzip2") {
|
|
216
|
+
throw new FsSafeError("helper-unavailable", `${kind} archives require a supported bundled native binding`);
|
|
217
|
+
}
|
|
262
218
|
if (kind === "tar") {
|
|
263
219
|
await withExtractionDeadline(params.timeoutMs, label, async (deadline) => {
|
|
264
220
|
const tar = await importOptionalTar();
|
|
@@ -269,6 +225,12 @@ export async function extractArchive(params) {
|
|
|
269
225
|
deadline,
|
|
270
226
|
});
|
|
271
227
|
try {
|
|
228
|
+
await preflightTarMetadata({
|
|
229
|
+
archivePath: stagedArchive.path,
|
|
230
|
+
maxMetaEntryBytes: limits.maxMetaEntryBytes,
|
|
231
|
+
signal: deadline.signal,
|
|
232
|
+
});
|
|
233
|
+
deadline.check();
|
|
272
234
|
const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
|
|
273
235
|
await withStagedArchiveDestination({
|
|
274
236
|
destinationRealDir,
|
|
@@ -278,23 +240,56 @@ export async function extractArchive(params) {
|
|
|
278
240
|
rootDir: destinationRealDir,
|
|
279
241
|
stripComponents: params.stripComponents,
|
|
280
242
|
limits,
|
|
243
|
+
entryFilter: params.entryFilter,
|
|
244
|
+
onFiltered: params.onFiltered,
|
|
281
245
|
});
|
|
246
|
+
const acceptedEntries = [];
|
|
282
247
|
// A canonical cwd is not enough here: tar can still follow
|
|
283
248
|
// pre-existing child symlinks in the live destination tree.
|
|
284
249
|
// Extract into a private staging dir first, then merge through
|
|
285
250
|
// the same safe-open boundary checks used by direct file writes.
|
|
286
|
-
|
|
287
|
-
file: stagedArchive.path,
|
|
251
|
+
const extractor = tar.x({
|
|
288
252
|
cwd: stagingDir,
|
|
289
253
|
strip: Math.max(0, Math.floor(params.stripComponents ?? 0)),
|
|
290
254
|
gzip: params.tarGzip,
|
|
291
255
|
signal: deadline.signal,
|
|
292
256
|
preservePaths: false,
|
|
257
|
+
noChmod: true,
|
|
258
|
+
preserveOwner: false,
|
|
293
259
|
strict: true,
|
|
260
|
+
maxMetaEntrySize: limits.maxMetaEntryBytes,
|
|
261
|
+
filter(_entryPath, entry) {
|
|
262
|
+
try {
|
|
263
|
+
const info = readTarEntryInfo(entry);
|
|
264
|
+
const accepted = checkTarEntrySafety(info);
|
|
265
|
+
if (accepted) {
|
|
266
|
+
const relPath = stripArchivePath(info.path, Math.max(0, Math.floor(params.stripComponents ?? 0)));
|
|
267
|
+
if (relPath) {
|
|
268
|
+
acceptedEntries.push({
|
|
269
|
+
path: relPath,
|
|
270
|
+
mode: resolveArchiveEntryMode({
|
|
271
|
+
kind: info.type === "Directory" || info.type === "GNUDumpDir"
|
|
272
|
+
? "directory"
|
|
273
|
+
: "file",
|
|
274
|
+
archivedMode: info.mode,
|
|
275
|
+
policy: params.entryModes,
|
|
276
|
+
}),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return accepted;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
// Abort through the parser so pipeline tears down both the
|
|
284
|
+
// archive reader and unpacker instead of leaving a paused
|
|
285
|
+
// stream behind after a policy rejection.
|
|
286
|
+
this.abort(error instanceof Error ? error : new Error(String(error)));
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
},
|
|
294
290
|
onReadEntry(entry) {
|
|
295
291
|
try {
|
|
296
292
|
deadline.check();
|
|
297
|
-
checkTarEntrySafety(readTarEntryInfo(entry));
|
|
298
293
|
}
|
|
299
294
|
catch (err) {
|
|
300
295
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -305,6 +300,22 @@ export async function extractArchive(params) {
|
|
|
305
300
|
}
|
|
306
301
|
},
|
|
307
302
|
});
|
|
303
|
+
try {
|
|
304
|
+
await pipeline(fsSync.createReadStream(stagedArchive.path), extractor, {
|
|
305
|
+
signal: deadline.signal,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
throw createPipelineTimeoutError(error, deadline);
|
|
310
|
+
}
|
|
311
|
+
for (const accepted of acceptedEntries) {
|
|
312
|
+
const outputPath = resolveArchiveOutputPath({
|
|
313
|
+
rootDir: stagingDir,
|
|
314
|
+
relPath: accepted.path,
|
|
315
|
+
originalPath: accepted.path,
|
|
316
|
+
});
|
|
317
|
+
await fs.chmod(outputPath, accepted.mode);
|
|
318
|
+
}
|
|
308
319
|
deadline.check();
|
|
309
320
|
await mergeExtractedTreeIntoDestination({
|
|
310
321
|
sourceDir: stagingDir,
|
|
@@ -327,16 +338,8 @@ export async function extractArchive(params) {
|
|
|
327
338
|
stripComponents: params.stripComponents,
|
|
328
339
|
limits: params.limits,
|
|
329
340
|
deadline,
|
|
341
|
+
entryModes: params.entryModes,
|
|
342
|
+
entryFilter: params.entryFilter,
|
|
343
|
+
onFiltered: params.onFiltered,
|
|
330
344
|
}));
|
|
331
345
|
}
|
|
332
|
-
async function importOptionalTar() {
|
|
333
|
-
try {
|
|
334
|
-
return await import("tar");
|
|
335
|
-
}
|
|
336
|
-
catch (err) {
|
|
337
|
-
throw missingOptionalArchiveDependencyError("tar", err);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function missingOptionalArchiveDependencyError(packageName, cause) {
|
|
341
|
-
return new Error(`Optional archive dependency "${packageName}" is not installed. Install it to use TAR archive helpers from @openclaw/fs-safe/archive.`, { cause });
|
|
342
|
-
}
|
package/dist/atomic.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
|
|
1
|
+
export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicRestoreCleanup, type ReplaceFileAtomicRestoreFailureDetails, type ReplaceFileAtomicResult, type ReplaceFileCopyFallbackRestorePolicy, type ReplaceFileDestinationHardlinkPolicy, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
|
|
2
2
|
export { writeTextAtomic, type WriteTextAtomicOptions } from "./text-atomic.js";
|
|
3
3
|
export { replaceDirectoryAtomic, type ReplaceDirectoryAtomicOptions } from "./replace-directory.js";
|
|
4
4
|
export { movePathWithCopyFallback, type MovePathWithCopyFallbackOptions } from "./move-path.js";
|
package/dist/atomic.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACpC,KAAK,sCAAsC,EAC3C,KAAK,uBAAuB,EAC5B,KAAK,oCAAoC,EACzC,KAAK,oCAAoC,EACzC,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { configureFsSafePython,
|
|
1
|
+
export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, type FsSafePythonConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
|
|
2
2
|
export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
|
|
3
3
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { configureFsSafePython,
|
|
1
|
+
export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
|
|
2
2
|
export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"containment.d.ts","sourceRoot":"","sources":["../src/containment.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/durability.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
|
|
2
|
+
export { isHardlinkFallbackError, publishFileExclusive, type PublishFileExclusiveResult, type PublishFileExclusiveStrategy, type PublishFileExclusiveCleanup, type PublishFileExclusiveDirectorySyncFailure, type PublishFileExclusiveFailureDetails, type PublishFileExclusiveFailurePhase, type PublishFileExclusiveSyncFailurePolicy, } from "./publish-file.js";
|
|
3
|
+
export { sha256File, type Sha256FileInput, type Sha256FileResult, } from "./file-hash.js";
|
|
2
4
|
//# sourceMappingURL=durability.d.ts.map
|
package/dist/durability.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
|
package/dist/durability.js
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
1
|
export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, } from "./directory-durability.js";
|
|
2
|
+
export { isHardlinkFallbackError, publishFileExclusive, } from "./publish-file.js";
|
|
3
|
+
export { sha256File, } from "./file-hash.js";
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
export type FsSafeErrorCode = "already-exists" | "denied-path" | "device-path" | "hardlink" | "helper-failed" | "helper-unavailable" | "invalid-path" | "insecure-permissions" | "not-empty" | "not-file" | "not-found" | "not-owned" | "not-removable" | "outside-workspace" | "path-alias" | "path-mismatch" | "permission-unverified" | "symlink" | "timeout" | "too-large" | "unsupported-platform";
|
|
1
|
+
export type FsSafeErrorCode = "already-exists" | "denied-path" | "device-path" | "hardlink" | "helper-failed" | "helper-unavailable" | "invalid-path" | "insecure-permissions" | "not-empty" | "not-file" | "not-found" | "not-owned" | "not-removable" | "outside-workspace" | "path-alias" | "path-mismatch" | "permission-unverified" | "secret-exists" | "store-reentrant-update" | "symlink" | "timeout" | "too-large" | "unsupported-platform";
|
|
2
2
|
export type FsSafeErrorCategory = "policy" | "operational";
|
|
3
|
+
export type FsSafeErrorDetails = Readonly<Record<string, unknown>>;
|
|
3
4
|
export declare function categorizeFsSafeError(code: FsSafeErrorCode): FsSafeErrorCategory;
|
|
4
5
|
export declare class FsSafeError extends Error {
|
|
5
6
|
readonly code: FsSafeErrorCode;
|
|
6
7
|
readonly category: FsSafeErrorCategory;
|
|
8
|
+
readonly details?: FsSafeErrorDetails;
|
|
7
9
|
constructor(code: FsSafeErrorCode, message: string, options?: {
|
|
8
10
|
cause?: unknown;
|
|
11
|
+
details?: FsSafeErrorDetails;
|
|
9
12
|
});
|
|
10
13
|
}
|
|
11
14
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,gBAAgB,GAChB,aAAa,GACb,aAAa,GACb,UAAU,GACV,eAAe,GACf,oBAAoB,GACpB,cAAc,GACd,sBAAsB,GACtB,WAAW,GACX,UAAU,GACV,WAAW,GACX,WAAW,GACX,eAAe,GACf,mBAAmB,GACnB,YAAY,GACZ,eAAe,GACf,uBAAuB,GACvB,SAAS,GACT,SAAS,GACT,WAAW,GACX,sBAAsB,CAAC;AAE3B,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,gBAAgB,GAChB,aAAa,GACb,aAAa,GACb,UAAU,GACV,eAAe,GACf,oBAAoB,GACpB,cAAc,GACd,sBAAsB,GACtB,WAAW,GACX,UAAU,GACV,WAAW,GACX,WAAW,GACX,eAAe,GACf,mBAAmB,GACnB,YAAY,GACZ,eAAe,GACf,uBAAuB,GACvB,eAAe,GACf,wBAAwB,GACxB,SAAS,GACT,SAAS,GACT,WAAW,GACX,sBAAsB,CAAC;AAE3B,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,aAAa,CAAC;AAC3D,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAUnE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,mBAAmB,CAEhF;AAED,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAEtC,YACE,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAAO,EAOhE;CACF"}
|
package/dist/errors.js
CHANGED
|
@@ -11,10 +11,12 @@ export function categorizeFsSafeError(code) {
|
|
|
11
11
|
export class FsSafeError extends Error {
|
|
12
12
|
code;
|
|
13
13
|
category;
|
|
14
|
+
details;
|
|
14
15
|
constructor(code, message, options = {}) {
|
|
15
16
|
super(message, options);
|
|
16
17
|
this.name = "FsSafeError";
|
|
17
18
|
this.code = code;
|
|
18
19
|
this.category = categorizeFsSafeError(code);
|
|
20
|
+
this.details = options.details;
|
|
19
21
|
}
|
|
20
22
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FileHandle } from "node:fs/promises";
|
|
2
|
+
import { type NativeBinding } from "./native.js";
|
|
3
|
+
export type Sha256FileInput = string | FileHandle;
|
|
4
|
+
export type Sha256FileResult = {
|
|
5
|
+
bytes: number;
|
|
6
|
+
digest: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined): Promise<Sha256FileResult>;
|
|
9
|
+
export declare function sha256File(input: Sha256FileInput): Promise<Sha256FileResult>;
|
|
10
|
+
//# sourceMappingURL=file-hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAcF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,GACrD,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AAsCD,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAElF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fsSync from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
5
|
+
import { sameFileIdentity } from "./file-identity.js";
|
|
6
|
+
import { getNativeBinding } from "./native.js";
|
|
7
|
+
function readFlags() {
|
|
8
|
+
return (fsSync.constants.O_RDONLY |
|
|
9
|
+
(process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
|
|
10
|
+
? fsSync.constants.O_NOFOLLOW
|
|
11
|
+
: 0) |
|
|
12
|
+
(process.platform !== "win32" && typeof fsSync.constants.O_NONBLOCK === "number"
|
|
13
|
+
? fsSync.constants.O_NONBLOCK
|
|
14
|
+
: 0));
|
|
15
|
+
}
|
|
16
|
+
export async function hashFileHandle(handle, native = getNativeBinding()) {
|
|
17
|
+
const stat = await handle.stat();
|
|
18
|
+
if (!stat.isFile()) {
|
|
19
|
+
throw new FsSafeError("not-file", "SHA-256 input is not a regular file");
|
|
20
|
+
}
|
|
21
|
+
if (native) {
|
|
22
|
+
return await native.sha256File(handle.fd);
|
|
23
|
+
}
|
|
24
|
+
const hash = createHash("sha256");
|
|
25
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
26
|
+
let position = 0;
|
|
27
|
+
while (true) {
|
|
28
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
|
|
29
|
+
if (bytesRead === 0) {
|
|
30
|
+
return { bytes: position, digest: hash.digest("hex") };
|
|
31
|
+
}
|
|
32
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
33
|
+
position += bytesRead;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function hashPath(filePath) {
|
|
37
|
+
const before = await fs.lstat(filePath);
|
|
38
|
+
if (before.isSymbolicLink()) {
|
|
39
|
+
throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link");
|
|
40
|
+
}
|
|
41
|
+
if (!before.isFile()) {
|
|
42
|
+
throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
|
|
43
|
+
}
|
|
44
|
+
let handle;
|
|
45
|
+
try {
|
|
46
|
+
handle = await fs.open(filePath, readFlags());
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error?.code === "ELOOP") {
|
|
50
|
+
throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link", {
|
|
51
|
+
cause: error,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const opened = await handle.stat();
|
|
58
|
+
const current = await fs.lstat(filePath);
|
|
59
|
+
if (!opened.isFile()) {
|
|
60
|
+
throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
|
|
61
|
+
}
|
|
62
|
+
if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(opened, current)) {
|
|
63
|
+
throw new FsSafeError("path-mismatch", "SHA-256 path changed while opening");
|
|
64
|
+
}
|
|
65
|
+
return await hashFileHandle(handle);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await handle.close().catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function sha256File(input) {
|
|
72
|
+
return typeof input === "string" ? await hashPath(input) : await hashFileHandle(input);
|
|
73
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Root } from "./root-impl.js";
|
|
2
|
+
import { type SidecarLockStaleSnapshot } from "./sidecar-lock-reclaim.js";
|
|
3
|
+
import type { SidecarLockCompromisedInfo, SidecarLockRetryOptions, SidecarLockStaleRecovery } from "./sidecar-lock-types.js";
|
|
4
|
+
export type FileLockSyncAcquireOptions<TPayload extends Record<string, unknown>> = {
|
|
5
|
+
lockPath?: string;
|
|
6
|
+
staleMs?: number;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
retry?: SidecarLockRetryOptions;
|
|
9
|
+
staleRecovery?: SidecarLockStaleRecovery;
|
|
10
|
+
reentrantOwner?: string;
|
|
11
|
+
payload: () => TPayload;
|
|
12
|
+
shouldReclaim?: (params: {
|
|
13
|
+
lockPath: string;
|
|
14
|
+
normalizedTargetPath: string;
|
|
15
|
+
payload: unknown;
|
|
16
|
+
staleMs: number;
|
|
17
|
+
nowMs: number;
|
|
18
|
+
heldByThisProcess: false;
|
|
19
|
+
}) => boolean;
|
|
20
|
+
shouldRemoveStaleLock?: (snapshot: SidecarLockStaleSnapshot) => boolean;
|
|
21
|
+
parsePayload?: (raw: string) => unknown;
|
|
22
|
+
lockRoot?: Root;
|
|
23
|
+
onCompromised?: (info: SidecarLockCompromisedInfo) => void;
|
|
24
|
+
compromiseCheckIntervalMs?: number;
|
|
25
|
+
};
|
|
26
|
+
export type FileLockSyncHandle = {
|
|
27
|
+
lockPath: string;
|
|
28
|
+
normalizedTargetPath: string;
|
|
29
|
+
verifyStillHeld(): boolean;
|
|
30
|
+
release(): void;
|
|
31
|
+
[Symbol.dispose](): void;
|
|
32
|
+
};
|
|
33
|
+
export declare function acquireFileLockSync<TPayload extends Record<string, unknown>>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>): FileLockSyncHandle;
|
|
34
|
+
export declare function withFileLockSync<T, TPayload extends Record<string, unknown>>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>, fn: () => T): T;
|
|
35
|
+
//# sourceMappingURL=file-lock-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAKnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AAiGF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CA6IpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
|