@openclaw/fs-safe 0.5.6 → 0.7.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 +89 -0
- package/README.md +23 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-crc32.d.ts +2 -0
- package/dist/archive-crc32.d.ts.map +1 -0
- package/dist/archive-crc32.js +14 -0
- package/dist/archive-deadline.d.ts +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +44 -8
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +1 -0
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +3 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +26 -16
- package/dist/archive-kind.js +2 -2
- package/dist/archive-limits.d.ts +11 -3
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +24 -0
- package/dist/archive-native.d.ts +3 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +31 -9
- package/dist/archive-policy.d.ts +2 -0
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +9 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +88 -41
- package/dist/archive-staging.d.ts +3 -0
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +91 -43
- package/dist/archive-tar-admission.d.ts +7 -0
- package/dist/archive-tar-admission.d.ts.map +1 -0
- package/dist/archive-tar-admission.js +43 -0
- package/dist/archive-tar-gnu.d.ts +2 -0
- package/dist/archive-tar-gnu.d.ts.map +1 -0
- package/dist/archive-tar-gnu.js +20 -0
- package/dist/archive-tar-header.d.ts +8 -0
- package/dist/archive-tar-header.d.ts.map +1 -0
- package/dist/archive-tar-header.js +47 -0
- package/dist/archive-tar-meta.d.ts +29 -1
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +135 -19
- package/dist/archive-tar-pax.d.ts +8 -0
- package/dist/archive-tar-pax.d.ts.map +1 -0
- package/dist/archive-tar-pax.js +100 -0
- package/dist/archive-tar-runtime.d.ts +4 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +3 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -2
- package/dist/archive-zip-admission.d.ts +7 -0
- package/dist/archive-zip-admission.d.ts.map +1 -0
- package/dist/archive-zip-admission.js +60 -0
- package/dist/archive-zip-count.d.ts +2 -0
- package/dist/archive-zip-count.d.ts.map +1 -0
- package/dist/archive-zip-count.js +140 -0
- package/dist/archive-zip-directory.d.ts +8 -0
- package/dist/archive-zip-directory.d.ts.map +1 -0
- package/dist/archive-zip-directory.js +224 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +1 -14
- package/dist/archive-zip-names.d.ts +12 -0
- package/dist/archive-zip-names.d.ts.map +1 -0
- package/dist/archive-zip-names.js +99 -0
- package/dist/archive-zip-preflight.d.ts +1 -1
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +8 -145
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +63 -42
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +2 -4
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +3 -10
- package/dist/byte-budget.d.ts +5 -0
- package/dist/byte-budget.d.ts.map +1 -0
- package/dist/byte-budget.js +9 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +25 -19
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +26 -9
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +9 -5
- package/dist/file-store-limit.d.ts +2 -0
- package/dist/file-store-limit.d.ts.map +1 -0
- package/dist/file-store-limit.js +8 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +38 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +26 -26
- package/dist/json-durable-queue-directory.d.ts +2 -0
- package/dist/json-durable-queue-directory.d.ts.map +1 -0
- package/dist/json-durable-queue-directory.js +20 -0
- package/dist/json-durable-queue-ownership.d.ts +14 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -0
- package/dist/json-durable-queue-ownership.js +168 -0
- package/dist/json-durable-queue-retirement.d.ts +9 -0
- package/dist/json-durable-queue-retirement.d.ts.map +1 -0
- package/dist/json-durable-queue-retirement.js +126 -0
- package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
- package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
- package/dist/json-durable-queue-transfer-lock.js +19 -0
- package/dist/json-durable-queue.d.ts +1 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +90 -57
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +27 -8
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -2
- package/dist/native-binding.d.ts +15 -3
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +4 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +22 -6
- package/dist/native-pinned-write-windows.d.ts +8 -0
- package/dist/native-pinned-write-windows.d.ts.map +1 -0
- package/dist/native-pinned-write-windows.js +92 -0
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +136 -127
- package/dist/native-staged-file.d.ts +24 -0
- package/dist/native-staged-file.d.ts.map +1 -0
- package/dist/native-staged-file.js +337 -0
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +4 -4
- package/dist/opened-realpath.d.ts +2 -0
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +12 -7
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +11 -110
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +4 -2
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -1
- package/dist/permission-exec.d.ts +19 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +57 -11
- package/dist/permissions-public.d.ts +1 -1
- package/dist/permissions-public.d.ts.map +1 -1
- package/dist/permissions-windows.d.ts +3 -0
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +16 -5
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts +6 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +26 -37
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +3 -2
- package/dist/private-temp-workspace.d.ts +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +81 -56
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -4
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +6 -4
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +136 -82
- package/dist/replace-file-copy-fallback.d.ts +3 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +21 -36
- package/dist/replace-file-copy-source.d.ts +21 -0
- package/dist/replace-file-copy-source.d.ts.map +1 -0
- package/dist/replace-file-copy-source.js +112 -0
- package/dist/replace-file-descriptor.d.ts +13 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +32 -7
- package/dist/replace-file-rename-policy.d.ts +7 -0
- package/dist/replace-file-rename-policy.d.ts.map +1 -0
- package/dist/replace-file-rename-policy.js +30 -0
- package/dist/replace-file-temp-owner.d.ts +46 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -0
- package/dist/replace-file-temp-owner.js +346 -0
- package/dist/replace-file.d.ts +6 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +72 -58
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +118 -98
- package/dist/root-paths.d.ts +11 -14
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +36 -27
- package/dist/root-write-verification.d.ts +11 -0
- package/dist/root-write-verification.d.ts.map +1 -0
- package/dist/root-write-verification.js +91 -0
- package/dist/secret-file.d.ts +1 -6
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +49 -120
- package/dist/secret-read-async.d.ts +1 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +51 -72
- package/dist/secret-read-policy.d.ts +13 -0
- package/dist/secret-read-policy.d.ts.map +1 -0
- package/dist/secret-read-policy.js +28 -0
- package/dist/secret.d.ts +2 -1
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +42 -29
- package/dist/sibling-staged-file.d.ts +15 -0
- package/dist/sibling-staged-file.d.ts.map +1 -0
- package/dist/sibling-staged-file.js +148 -0
- package/dist/sibling-temp.d.ts +3 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +30 -74
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +48 -27
- package/dist/sidecar-lock-handle.d.ts +6 -2
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +17 -3
- 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 +29 -0
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +29 -7
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +28 -16
- package/dist/staged-directory.d.ts +16 -0
- package/dist/staged-directory.d.ts.map +1 -0
- package/dist/staged-directory.js +60 -0
- package/dist/staged-file-types.d.ts +56 -0
- package/dist/staged-file-types.d.ts.map +1 -0
- package/dist/staged-file-types.js +1 -0
- package/dist/staged-file.d.ts +10 -0
- package/dist/staged-file.d.ts.map +1 -0
- package/dist/staged-file.js +15 -0
- package/dist/strict-file-identity.d.ts +6 -0
- package/dist/strict-file-identity.d.ts.map +1 -0
- package/dist/strict-file-identity.js +48 -0
- package/dist/suppressed-error.d.ts +6 -0
- package/dist/suppressed-error.d.ts.map +1 -0
- package/dist/suppressed-error.js +15 -0
- package/dist/temp-cleanup.d.ts +2 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +25 -10
- package/dist/temp-workspace-owner.d.ts +23 -0
- package/dist/temp-workspace-owner.d.ts.map +1 -0
- package/dist/temp-workspace-owner.js +320 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +5 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/windows-owner.d.ts +3 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +10 -2
- package/docs/advanced.md +19 -2
- package/docs/archive.md +250 -35
- package/docs/atomic.md +11 -2
- package/docs/config.md +7 -0
- package/docs/contributing.md +45 -7
- package/docs/durability.md +18 -5
- package/docs/errors.md +16 -1
- package/docs/file-store.md +2 -0
- package/docs/index.md +3 -1
- package/docs/install.md +20 -8
- package/docs/json.md +8 -4
- package/docs/migrating-to-0.5.md +7 -7
- package/docs/migrating-to-0.6.md +43 -0
- package/docs/native-helper.md +30 -8
- package/docs/native.md +73 -17
- package/docs/output.md +10 -0
- package/docs/path-scope.md +28 -2
- package/docs/permissions.md +13 -2
- package/docs/public-api.md +6 -3
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +1 -1
- package/docs/regular-file.md +9 -2
- package/docs/root.md +3 -1
- package/docs/secret-file.md +12 -0
- package/docs/secure-file.md +21 -3
- package/docs/security-model.md +33 -1
- package/docs/sidecar-lock.md +15 -1
- package/docs/staged-file.md +178 -0
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- package/docs/testing.md +1 -1
- package/docs/writing.md +19 -0
- package/package.json +16 -9
- 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
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
4
|
+
import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
|
|
5
|
+
import { acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
|
|
4
6
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
7
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
5
8
|
import { replaceFileAtomic } from "./replace-file.js";
|
|
6
9
|
import { assertSafePathSegment } from "./safe-path-segment.js";
|
|
10
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
7
11
|
export const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES = 16 * 1024 * 1024;
|
|
8
12
|
function getErrnoCode(error) {
|
|
9
13
|
return error && typeof error === "object" && "code" in error
|
|
@@ -46,6 +50,7 @@ export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
|
|
|
46
50
|
return {
|
|
47
51
|
jsonPath: path.join(queueDir, `${id}.json`),
|
|
48
52
|
deliveredPath: path.join(queueDir, `${id}.delivered`),
|
|
53
|
+
processingPath: path.join(queueDir, `${id}.processing`),
|
|
49
54
|
};
|
|
50
55
|
}
|
|
51
56
|
export async function ensureJsonDurableQueueDirs(params) {
|
|
@@ -61,6 +66,7 @@ async function ensureJsonDurableQueueDir(dir, validationRoot) {
|
|
|
61
66
|
await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
62
67
|
await assertNoSymlinkDirectorySegments(root, dir, false);
|
|
63
68
|
await chmodQueueDirectory(dir);
|
|
69
|
+
await syncQueueDirectoryCreation(dir, root.path);
|
|
64
70
|
}
|
|
65
71
|
async function assertJsonDurableQueueDir(dir, validationRoot) {
|
|
66
72
|
const root = validationRoot
|
|
@@ -208,35 +214,55 @@ export async function writeJsonDurableQueueEntry(params) {
|
|
|
208
214
|
content: stringifyJsonDocument(params.entry, null, 2),
|
|
209
215
|
mode: 0o600,
|
|
210
216
|
tempPrefix: params.tempPrefix,
|
|
217
|
+
syncTempFile: true,
|
|
218
|
+
syncParentDir: true,
|
|
211
219
|
});
|
|
212
220
|
}
|
|
213
|
-
async function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
async function replaceJsonDurableQueueEntry(params) {
|
|
222
|
+
await replaceFileAtomic({
|
|
223
|
+
filePath: params.filePath,
|
|
224
|
+
content: stringifyJsonDocument(params.entry, null, 2),
|
|
225
|
+
mode: 0o600,
|
|
226
|
+
tempPrefix: params.tempPrefix,
|
|
227
|
+
syncTempFile: true,
|
|
228
|
+
syncParentDir: true,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
async function inspectQueueEntry(inspect, maxBytes, expected) {
|
|
232
|
+
let inspectionFailed = false;
|
|
233
|
+
try {
|
|
234
|
+
return await inspectFileIdentity(async () => {
|
|
235
|
+
try {
|
|
236
|
+
const stat = await inspect();
|
|
237
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
238
|
+
throw new Error("queue entry is not a regular file");
|
|
239
|
+
}
|
|
240
|
+
if (stat.nlink > 1n)
|
|
241
|
+
throw new Error("queue entry hardlinks are not allowed");
|
|
242
|
+
if (stat.size > maxBytes)
|
|
243
|
+
throw new Error(`queue entry exceeds ${maxBytes} bytes`);
|
|
244
|
+
return stat;
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
inspectionFailed = true;
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}, expected);
|
|
220
251
|
}
|
|
221
|
-
|
|
222
|
-
|
|
252
|
+
catch (error) {
|
|
253
|
+
// Translate only the identity helper's rejection, never inspection errors.
|
|
254
|
+
if (inspectionFailed)
|
|
255
|
+
throw error;
|
|
256
|
+
throw new Error("queue entry changed during read", { cause: error });
|
|
223
257
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
258
|
+
}
|
|
259
|
+
async function readBoundedUtf8File(params) {
|
|
260
|
+
const inspectPath = () => fs.promises.lstat(params.filePath, { bigint: true });
|
|
261
|
+
const initialStat = await inspectQueueEntry(inspectPath, params.maxBytes);
|
|
262
|
+
const handle = await fs.promises.open(params.filePath, resolveReadOpenFlags());
|
|
228
263
|
try {
|
|
229
|
-
const openedStat = await handle.stat();
|
|
230
|
-
|
|
231
|
-
if (!openedStat.isFile() ||
|
|
232
|
-
pathStat.isSymbolicLink() ||
|
|
233
|
-
!pathStat.isFile() ||
|
|
234
|
-
openedStat.nlink > 1 ||
|
|
235
|
-
pathStat.nlink > 1 ||
|
|
236
|
-
!sameFileIdentity(initialStat, openedStat) ||
|
|
237
|
-
!sameFileIdentity(pathStat, openedStat)) {
|
|
238
|
-
throw new Error("queue entry changed during read");
|
|
239
|
-
}
|
|
264
|
+
const openedStat = await inspectQueueEntry(() => handle.stat({ bigint: true }), params.maxBytes, initialStat);
|
|
265
|
+
await inspectQueueEntry(inspectPath, params.maxBytes, openedStat);
|
|
240
266
|
const chunks = [];
|
|
241
267
|
const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, params.maxBytes + 1));
|
|
242
268
|
let total = 0;
|
|
@@ -259,35 +285,26 @@ async function readBoundedUtf8File(params) {
|
|
|
259
285
|
export async function readJsonDurableQueueEntry(filePath, options = {}) {
|
|
260
286
|
return JSON.parse(await readBoundedUtf8File({
|
|
261
287
|
filePath,
|
|
262
|
-
maxBytes: options.maxBytes
|
|
288
|
+
maxBytes: normalizeMaxBytes(options.maxBytes, {
|
|
289
|
+
defaultValue: DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES,
|
|
290
|
+
}),
|
|
263
291
|
}));
|
|
264
292
|
}
|
|
265
293
|
export async function ackJsonDurableQueueEntry(paths) {
|
|
266
|
-
|
|
267
|
-
await fs.promises.rename(paths.jsonPath, paths.deliveredPath);
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
if (getErrnoCode(error) === "ENOENT") {
|
|
271
|
-
await unlinkBestEffort(paths.deliveredPath);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
throw error;
|
|
275
|
-
}
|
|
276
|
-
await unlinkBestEffort(paths.deliveredPath);
|
|
294
|
+
await acknowledgeDurableQueueEntry(paths);
|
|
277
295
|
}
|
|
278
296
|
export async function loadJsonDurableQueueEntry(params) {
|
|
279
297
|
try {
|
|
280
|
-
const
|
|
281
|
-
if (!
|
|
298
|
+
const claimedPath = await claimDurableQueueEntry(params.paths);
|
|
299
|
+
if (!claimedPath)
|
|
282
300
|
return null;
|
|
283
|
-
|
|
284
|
-
const raw = await readJsonDurableQueueEntry(params.paths.jsonPath, {
|
|
301
|
+
const raw = await readJsonDurableQueueEntry(claimedPath, {
|
|
285
302
|
maxBytes: params.maxBytes,
|
|
286
303
|
});
|
|
287
304
|
const result = params.read ? await params.read(raw, params.paths.jsonPath) : { entry: raw };
|
|
288
305
|
if (result.migrated) {
|
|
289
|
-
await
|
|
290
|
-
filePath:
|
|
306
|
+
await replaceJsonDurableQueueEntry({
|
|
307
|
+
filePath: claimedPath,
|
|
291
308
|
entry: result.entry,
|
|
292
309
|
tempPrefix: params.tempPrefix,
|
|
293
310
|
});
|
|
@@ -315,28 +332,41 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
315
332
|
const now = Date.now();
|
|
316
333
|
for (const file of files) {
|
|
317
334
|
if (file.endsWith(".delivered")) {
|
|
318
|
-
|
|
335
|
+
const id = file.slice(0, -".delivered".length);
|
|
336
|
+
await completeDeliveredQueueEntry(resolveJsonDurableQueueEntryPaths(options.queueDir, id));
|
|
319
337
|
}
|
|
320
338
|
else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {
|
|
321
339
|
await unlinkStaleTmpBestEffort(path.join(options.queueDir, file), now, options.cleanupTmpMaxAgeMs);
|
|
322
340
|
}
|
|
323
341
|
}
|
|
324
|
-
const
|
|
342
|
+
const ids = [];
|
|
343
|
+
const seenIds = new Set();
|
|
325
344
|
for (const file of files) {
|
|
326
|
-
|
|
345
|
+
const suffix = file.endsWith(".processing")
|
|
346
|
+
? ".processing"
|
|
347
|
+
: file.endsWith(".json")
|
|
348
|
+
? ".json"
|
|
349
|
+
: null;
|
|
350
|
+
if (!suffix)
|
|
327
351
|
continue;
|
|
328
|
-
|
|
329
|
-
|
|
352
|
+
const id = file.slice(0, -suffix.length);
|
|
353
|
+
if (seenIds.has(id))
|
|
354
|
+
continue;
|
|
355
|
+
seenIds.add(id);
|
|
356
|
+
ids.push(id);
|
|
357
|
+
}
|
|
358
|
+
const entries = [];
|
|
359
|
+
for (const id of ids) {
|
|
330
360
|
try {
|
|
331
|
-
const
|
|
332
|
-
|
|
361
|
+
const paths = resolveJsonDurableQueueEntryPaths(options.queueDir, id);
|
|
362
|
+
const claimedPath = await claimDurableQueueEntry(paths);
|
|
363
|
+
if (!claimedPath)
|
|
333
364
|
continue;
|
|
334
|
-
}
|
|
335
|
-
const
|
|
336
|
-
const result = options.read ? await options.read(raw, filePath) : { entry: raw };
|
|
365
|
+
const raw = await readJsonDurableQueueEntry(claimedPath, { maxBytes: options.maxBytes });
|
|
366
|
+
const result = options.read ? await options.read(raw, paths.jsonPath) : { entry: raw };
|
|
337
367
|
if (result.migrated) {
|
|
338
|
-
await
|
|
339
|
-
filePath,
|
|
368
|
+
await replaceJsonDurableQueueEntry({
|
|
369
|
+
filePath: claimedPath,
|
|
340
370
|
entry: result.entry,
|
|
341
371
|
tempPrefix: options.tempPrefix,
|
|
342
372
|
});
|
|
@@ -354,5 +384,8 @@ export async function moveJsonDurableQueueEntryToFailed(params) {
|
|
|
354
384
|
const roots = await queueValidationRoots(params.queueDir, params.failedDir);
|
|
355
385
|
await assertJsonDurableQueueDir(params.queueDir, roots.queueRoot);
|
|
356
386
|
await ensureJsonDurableQueueDir(params.failedDir, roots.failedRoot);
|
|
357
|
-
await
|
|
387
|
+
await moveDurableQueueEntryToFailed({
|
|
388
|
+
paths: resolveJsonDurableQueueEntryPaths(params.queueDir, params.id),
|
|
389
|
+
failedPath: path.join(params.failedDir, `${params.id}.json`),
|
|
390
|
+
});
|
|
358
391
|
}
|
package/dist/json.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAO7B,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE5E,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AA8JhF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EACzC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAoB,GAC5B,CAAC,GAAG,IAAI,CAUV;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,QAmB5D;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAElC,YAAY,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,EAKrE;CACF;AAED,MAAM,MAAM,4BAA4B,CAAC,CAAC,IACtC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC9E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE,GAC3D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,MAAM,MAAM,iCAAiC,CAAC,CAAC,IAAI;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC;IAC1C,cAAc,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;CAC9D,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,iCAAiC,CAAC,OAAO,CAAC,EAC1C,OAAO,GAAG,UAAU,GAAG,gBAAgB,CACxC,CAAC;AAgBF,wBAAgB,0BAA0B,CAAC,CAAC,EAC1C,OAAO,EAAE,iCAAiC,CAAC,CAAC,CAAC,GAC5C,4BAA4B,CAAC,CAAC,CAAC,CA4CjC;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAC1C,OAAO,EAAE,uBAAuB,GAC/B,4BAA4B,CAAC,CAAC,CAAC,CAKjC;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,4BAA4B,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAOvD;AAED,wBAAsB,WAAW,CAAC,CAAC,EACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAWnB;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,CAAC,CAAC,CAiB7F;AAED,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAmBnB;AAED,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAoB,GAC5B,CAAC,CAYH;AAED,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,sBAAsB,EACtB,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,iBAAiB,CACnD,CAAC;AAEF,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,gBAAgB,iBAS3B"}
|
package/dist/json.js
CHANGED
|
@@ -3,9 +3,11 @@ import fsSync from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
|
+
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
6
7
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
7
8
|
import { readRegularFile, readRegularFileSync, statRegularFile } from "./regular-file.js";
|
|
8
9
|
import { openRootFileSync } from "./root-file.js";
|
|
10
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
9
11
|
import { writeTextAtomic } from "./text-atomic.js";
|
|
10
12
|
import { sleep } from "./timing.js";
|
|
11
13
|
const READ_RETRY_MAX_ATTEMPTS = 5;
|
|
@@ -48,19 +50,35 @@ async function readRegularFileIfExistsWithRetry(filePath, options = {}) {
|
|
|
48
50
|
}
|
|
49
51
|
const JSON_FILE_MODE = 0o600;
|
|
50
52
|
const JSON_DIR_MODE = 0o700;
|
|
51
|
-
const SUPPORTS_SYNC_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsSync.constants;
|
|
52
53
|
function getErrorCode(err) {
|
|
53
54
|
return err instanceof Error ? err.code : undefined;
|
|
54
55
|
}
|
|
55
|
-
function trySetSecureMode(pathname) {
|
|
56
|
+
function trySetSecureMode(pathname, expectedIdentity) {
|
|
56
57
|
let fd;
|
|
57
58
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const before = fsSync.lstatSync(pathname, { bigint: true });
|
|
60
|
+
if (before.isSymbolicLink() ||
|
|
61
|
+
!before.isFile() ||
|
|
62
|
+
before.nlink !== 1n ||
|
|
63
|
+
!sameFileIdentityForCleanup(before, expectedIdentity)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
fd = fsSync.openSync(pathname, resolveReadOpenFlags());
|
|
67
|
+
const opened = fsSync.fstatSync(fd, { bigint: true });
|
|
68
|
+
const current = fsSync.lstatSync(pathname, { bigint: true });
|
|
69
|
+
if (!opened.isFile() ||
|
|
70
|
+
opened.nlink !== 1n ||
|
|
71
|
+
current.isSymbolicLink() ||
|
|
72
|
+
!current.isFile() ||
|
|
73
|
+
current.nlink !== 1n ||
|
|
74
|
+
!sameFileIdentityForCleanup(opened, expectedIdentity) ||
|
|
75
|
+
!sameFileIdentityForCleanup(current, opened)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
60
78
|
fsSync.fchmodSync(fd, JSON_FILE_MODE);
|
|
61
79
|
}
|
|
62
80
|
catch {
|
|
63
|
-
//
|
|
81
|
+
// Best-effort mode application never falls back to a mutable pathname.
|
|
64
82
|
}
|
|
65
83
|
finally {
|
|
66
84
|
if (fd !== undefined) {
|
|
@@ -129,6 +147,7 @@ function writeTempJsonFile(pathname, payload) {
|
|
|
129
147
|
try {
|
|
130
148
|
fsSync.writeFileSync(fd, payload, "utf8");
|
|
131
149
|
fsSync.fsyncSync(fd);
|
|
150
|
+
return fsSync.fstatSync(fd, { bigint: true });
|
|
132
151
|
}
|
|
133
152
|
finally {
|
|
134
153
|
fsSync.closeSync(fd);
|
|
@@ -152,10 +171,10 @@ export function writeJsonSync(pathname, data) {
|
|
|
152
171
|
const payload = `${stringifyJsonDocument(data, null, 2)}\n`;
|
|
153
172
|
fsSync.mkdirSync(path.dirname(targetPath), { recursive: true, mode: JSON_DIR_MODE });
|
|
154
173
|
try {
|
|
155
|
-
writeTempJsonFile(tmpPath, payload);
|
|
156
|
-
trySetSecureMode(tmpPath);
|
|
174
|
+
const tempIdentity = writeTempJsonFile(tmpPath, payload);
|
|
175
|
+
trySetSecureMode(tmpPath, tempIdentity);
|
|
157
176
|
renameJsonFileWithFallback(tmpPath, targetPath);
|
|
158
|
-
trySetSecureMode(targetPath);
|
|
177
|
+
trySetSecureMode(targetPath, tempIdentity);
|
|
159
178
|
trySyncDirectory(targetPath);
|
|
160
179
|
}
|
|
161
180
|
finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAOA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAqGF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA6B7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA4CtC"}
|
package/dist/local-roots.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
4
5
|
import { expandHomePrefix, resolveUserPath } from "./home-dir.js";
|
|
5
6
|
import { isFileUrl, safeFileURLToPath } from "./local-file-access.js";
|
|
@@ -129,6 +130,7 @@ export function resolveLocalPathFromRootsSync(options) {
|
|
|
129
130
|
return null;
|
|
130
131
|
}
|
|
131
132
|
export async function readLocalFileFromRoots(options) {
|
|
133
|
+
const maxBytes = normalizeMaxBytes(options.maxBytes);
|
|
132
134
|
const label = options.label ?? "local roots";
|
|
133
135
|
const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
|
|
134
136
|
const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
|
|
@@ -147,8 +149,8 @@ export async function readLocalFileFromRoots(options) {
|
|
|
147
149
|
};
|
|
148
150
|
// Leave maxBytes absent when the caller omits it so Root's own default
|
|
149
151
|
// cap remains in force instead of being overwritten by undefined.
|
|
150
|
-
if (
|
|
151
|
-
readOptions.maxBytes =
|
|
152
|
+
if (maxBytes !== undefined) {
|
|
153
|
+
readOptions.maxBytes = maxBytes;
|
|
152
154
|
}
|
|
153
155
|
// A trusted root symlink has two valid spellings. Preserve the caller's
|
|
154
156
|
// lexical spelling when possible so Root.read() still enforces its
|
package/dist/native-binding.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TarMeterLimits } from "./archive-limits.js";
|
|
1
2
|
export interface NativeFileHash {
|
|
2
3
|
bytes: number;
|
|
3
4
|
digest: string;
|
|
@@ -31,6 +32,11 @@ export interface NativeOpenBeneathResult {
|
|
|
31
32
|
fd: number;
|
|
32
33
|
containment: "kernel-atomic" | "best-effort";
|
|
33
34
|
}
|
|
35
|
+
export interface NativeOwnedTreeRemovalResult {
|
|
36
|
+
outcome?: "removed" | "preserved";
|
|
37
|
+
errorCode?: string;
|
|
38
|
+
errorMessage?: string;
|
|
39
|
+
}
|
|
34
40
|
export interface NativeWindowsAccessControlEntry {
|
|
35
41
|
sid: string;
|
|
36
42
|
mask: number;
|
|
@@ -62,17 +68,23 @@ export interface NativeWindowsSecurityFacts {
|
|
|
62
68
|
aces: NativeWindowsAccessControlEntry[];
|
|
63
69
|
}
|
|
64
70
|
export interface NativeBinding {
|
|
71
|
+
createStagedFile?(parentFd: number, basename: string): number;
|
|
72
|
+
stagedFileMatches?(parentFd: number, basename: string, fileFd: number): boolean;
|
|
73
|
+
removeStagedFile?(parentFd: number, basename: string, fileFd: number): "removed" | "name-absent" | "preserved";
|
|
65
74
|
cloneFileExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
|
|
66
75
|
copyFileRangeExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
|
|
67
76
|
createPrivateDirectory(path: string): void;
|
|
68
|
-
extractArchiveNative(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[],
|
|
77
|
+
extractArchiveNative(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
|
|
69
78
|
fstatIdentity(fd: number): NativeFileIdentity;
|
|
70
|
-
inspectArchiveNative(path: string, kind: string,
|
|
79
|
+
inspectArchiveNative(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
|
|
71
80
|
linkBeneath(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
|
|
72
81
|
mkdirBeneath(rootFd: number, relPath: string, mode: number): void;
|
|
73
82
|
openBeneath(rootFd: number, relPath: string, flags: number): NativeOpenBeneathResult;
|
|
74
|
-
readArchiveEntryNative(path: string, kind: string, requested: string, maxBytes: number,
|
|
83
|
+
readArchiveEntryNative(path: string, kind: string, requested: string, maxBytes: number, limits: TarMeterLimits, signal: AbortSignal): Promise<Buffer>;
|
|
75
84
|
readOwnerAndDacl(path: string): NativeWindowsSecurityFacts;
|
|
85
|
+
ownedTreeRemovalAvailable?(parentFd: number): boolean;
|
|
86
|
+
removeOwnedTree?(parentFd: number, basename: string, directoryFd: number): Promise<NativeOwnedTreeRemovalResult>;
|
|
87
|
+
removeOwnedTreeSync?(parentFd: number, basename: string, directoryFd: number): NativeOwnedTreeRemovalResult;
|
|
76
88
|
renameNoReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
|
|
77
89
|
renameReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
|
|
78
90
|
sha256File(fd: number): Promise<NativeFileHash>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;CAAG;AAErE,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,eAAe,GAAG,aAAa,CAAC;CAC9C;AAED,MAAM,WAAW,+BAA+B;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,aAAa,EAAE,OAAO,CAAC;QACvB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,kBAAkB,EAAE,OAAO,CAAC;QAC5B,WAAW,EAAE,OAAO,CAAC;QACrB,SAAS,EAAE,OAAO,CAAC;QACnB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,+BAA+B,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;
|
|
1
|
+
{"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Stats } from "node:fs";
|
|
2
2
|
import type { ContainmentGuarantee } from "./containment.js";
|
|
3
3
|
import { type NativeBinding } from "./native.js";
|
|
4
|
+
import type { PinnedWriteInput } from "./pinned-write.js";
|
|
4
5
|
export type NativeFileHandle = {
|
|
5
6
|
readonly fd: number;
|
|
6
7
|
readonly containment: ContainmentGuarantee;
|
|
@@ -8,6 +9,9 @@ export type NativeFileHandle = {
|
|
|
8
9
|
stat(): Promise<Stats>;
|
|
9
10
|
writeFile(data: string | Buffer, encoding?: BufferEncoding): Promise<void>;
|
|
10
11
|
};
|
|
12
|
+
export declare function nativeOpenFlags(flags: number): number;
|
|
13
|
+
export declare function writeNativeFd(fd: number, data: Buffer): void;
|
|
14
|
+
export declare function writeNativeInput(fd: number, input: PinnedWriteInput, maxBytes?: number): Promise<void>;
|
|
11
15
|
export declare function removeNativeCreatedFileIfStillPinned(params: {
|
|
12
16
|
binding: NativeBinding;
|
|
13
17
|
parentPath: string;
|
|
@@ -17,5 +21,4 @@ export declare function removeNativeCreatedFileIfStillPinned(params: {
|
|
|
17
21
|
}): void;
|
|
18
22
|
export declare function createNativeExclusiveFile(targetPath: string, mode: number): Promise<NativeFileHandle | undefined>;
|
|
19
23
|
export declare function syncNativeFileBestEffort(fd: number): void;
|
|
20
|
-
export declare function writeNativeFd(fd: number, data: Buffer): void;
|
|
21
24
|
//# sourceMappingURL=native-operations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAS5D;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAsBD,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB,GAAG,IAAI,CAsBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAyDvC;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQzD"}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import fsSync, {} from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
4
5
|
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
5
6
|
import { getNativeBinding } from "./native.js";
|
|
6
|
-
function nativeOpenFlags(flags) {
|
|
7
|
+
export function nativeOpenFlags(flags) {
|
|
7
8
|
const closeOnExec = fsSync.constants.O_CLOEXEC;
|
|
8
9
|
return (flags |
|
|
9
10
|
(closeOnExec ?? 0) |
|
|
10
11
|
(typeof fsSync.constants.O_NOFOLLOW === "number" ? fsSync.constants.O_NOFOLLOW : 0));
|
|
11
12
|
}
|
|
12
|
-
function
|
|
13
|
+
export function writeNativeFd(fd, data) {
|
|
13
14
|
let offset = 0;
|
|
14
15
|
while (offset < data.byteLength) {
|
|
15
16
|
const written = fsSync.writeSync(fd, data, offset, data.byteLength - offset);
|
|
@@ -19,6 +20,24 @@ function writeAll(fd, data) {
|
|
|
19
20
|
offset += written;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
23
|
+
export async function writeNativeInput(fd, input, maxBytes) {
|
|
24
|
+
let bytes = 0;
|
|
25
|
+
const write = (data) => {
|
|
26
|
+
bytes += data.byteLength;
|
|
27
|
+
if (maxBytes !== undefined && bytes > maxBytes) {
|
|
28
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
29
|
+
}
|
|
30
|
+
writeNativeFd(fd, data);
|
|
31
|
+
};
|
|
32
|
+
if (input.kind === "buffer") {
|
|
33
|
+
write(typeof input.data === "string" ? Buffer.from(input.data, input.encoding ?? "utf8") : input.data);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
for await (const chunk of input.stream) {
|
|
37
|
+
write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
22
41
|
function wrapNativeFd(fd, containment) {
|
|
23
42
|
let open = true;
|
|
24
43
|
return {
|
|
@@ -34,7 +53,7 @@ function wrapNativeFd(fd, containment) {
|
|
|
34
53
|
return fsSync.fstatSync(fd);
|
|
35
54
|
},
|
|
36
55
|
async writeFile(data, encoding) {
|
|
37
|
-
|
|
56
|
+
writeNativeFd(fd, Buffer.isBuffer(data) ? data : Buffer.from(data, encoding ?? "utf8"));
|
|
38
57
|
},
|
|
39
58
|
};
|
|
40
59
|
}
|
|
@@ -123,6 +142,3 @@ export function syncNativeFileBestEffort(fd) {
|
|
|
123
142
|
}
|
|
124
143
|
}
|
|
125
144
|
}
|
|
126
|
-
export function writeNativeFd(fd, data) {
|
|
127
|
-
writeAll(fd, data);
|
|
128
|
-
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FileHandle } from "node:fs/promises";
|
|
2
|
+
import type { AsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
|
+
import type { FileIdentityStat } from "./file-identity.js";
|
|
4
|
+
import type { NativeBinding } from "./native.js";
|
|
5
|
+
import type { PinnedWriteParams } from "./pinned-write.js";
|
|
6
|
+
export declare function sameNativeIdentity(left: Pick<FileIdentityStat, "dev" | "ino">, right: Pick<FileIdentityStat, "dev" | "ino">): boolean;
|
|
7
|
+
export declare function runPinnedWriteWindows(binding: NativeBinding, params: PinnedWriteParams, root: FileHandle, parentFd: number, parentGuard: AsyncDirectoryGuard): Promise<FileIdentityStat>;
|
|
8
|
+
//# sourceMappingURL=native-pinned-write-windows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-pinned-write-windows.d.ts","sourceRoot":"","sources":["../src/native-pinned-write-windows.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAO3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,EAC3C,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,GAC3C,OAAO,CAET;AAWD,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,mBAAmB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAgF3B"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fsSync, {} from "node:fs";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, syncNativeFileBestEffort, writeNativeInput, } from "./native-operations.js";
|
|
5
|
+
export function sameNativeIdentity(left, right) {
|
|
6
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
7
|
+
}
|
|
8
|
+
function closeWriteFd(fd) {
|
|
9
|
+
if (fd === undefined)
|
|
10
|
+
return;
|
|
11
|
+
try {
|
|
12
|
+
fsSync.closeSync(fd);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuard) {
|
|
19
|
+
const parentPath = parentGuard.realPath;
|
|
20
|
+
let tempFd;
|
|
21
|
+
let targetFd;
|
|
22
|
+
let tempIdentity;
|
|
23
|
+
let tempName = "";
|
|
24
|
+
let renamed = false;
|
|
25
|
+
let completed = false;
|
|
26
|
+
try {
|
|
27
|
+
tempName = `.${params.basename}.${randomUUID()}.native.tmp`;
|
|
28
|
+
tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
|
|
29
|
+
tempIdentity = fsSync.fstatSync(tempFd);
|
|
30
|
+
const verificationIdentity = fsSync.fstatSync(tempFd, { bigint: true });
|
|
31
|
+
// Creation is requested at 0600 in the binding, but a restrictive umask
|
|
32
|
+
// can remove owner access. Keep the unpublished inode private and
|
|
33
|
+
// reopenable until the published name has been identity-fenced.
|
|
34
|
+
fsSync.fchmodSync(tempFd, 0o600);
|
|
35
|
+
await writeNativeInput(tempFd, params.input, params.maxBytes);
|
|
36
|
+
syncNativeFileBestEffort(tempFd);
|
|
37
|
+
if (params.overwrite === false) {
|
|
38
|
+
binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
binding.renameReplace(parentFd, tempName, parentFd, params.basename);
|
|
42
|
+
}
|
|
43
|
+
renamed = true;
|
|
44
|
+
targetFd = binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
|
|
45
|
+
const targetIdentity = binding.fstatIdentity(targetFd);
|
|
46
|
+
if (!targetIdentity.isFile || !sameNativeIdentity(tempIdentity, targetIdentity)) {
|
|
47
|
+
throw new FsSafeError("path-mismatch", "native write target changed after rename");
|
|
48
|
+
}
|
|
49
|
+
// Native exclusive creation starts at 0600. Apply the requested mode only
|
|
50
|
+
// after reopening and fencing the published name, both so mode 000 stays
|
|
51
|
+
// verifiable and so broader modes are never exposed before that fence.
|
|
52
|
+
try {
|
|
53
|
+
fsSync.fchmodSync(targetFd, params.mode);
|
|
54
|
+
syncNativeFileBestEffort(targetFd);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
closeWriteFd(targetFd);
|
|
58
|
+
targetFd = undefined;
|
|
59
|
+
removeNativeCreatedFileIfStillPinned({
|
|
60
|
+
binding,
|
|
61
|
+
parentPath,
|
|
62
|
+
parentFd,
|
|
63
|
+
basename: params.basename,
|
|
64
|
+
created: tempIdentity,
|
|
65
|
+
});
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
syncNativeFileBestEffort(parentFd);
|
|
69
|
+
// Verification follows publication and final chmod, outside rollback handling.
|
|
70
|
+
await params.verifyPublished?.(targetFd, verificationIdentity, parentGuard);
|
|
71
|
+
completed = true;
|
|
72
|
+
return { dev: targetIdentity.dev, ino: targetIdentity.ino };
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
const targetCloseError = closeWriteFd(targetFd);
|
|
76
|
+
const tempCloseError = closeWriteFd(tempFd);
|
|
77
|
+
if (!renamed) {
|
|
78
|
+
removeNativeCreatedFileIfStillPinned({
|
|
79
|
+
binding,
|
|
80
|
+
parentPath,
|
|
81
|
+
parentFd,
|
|
82
|
+
basename: tempName,
|
|
83
|
+
created: tempIdentity,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const parentCloseError = closeWriteFd(parentFd);
|
|
87
|
+
await root.close().catch(() => undefined);
|
|
88
|
+
const closeError = targetCloseError ?? tempCloseError ?? parentCloseError;
|
|
89
|
+
if (completed && closeError !== undefined)
|
|
90
|
+
throw closeError;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmFvH"}
|