@openclaw/fs-safe 0.7.2 → 0.8.1
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 +71 -0
- package/README.md +5 -1
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +9 -10
- package/dist/archive-merge.d.ts +18 -0
- package/dist/archive-merge.d.ts.map +1 -0
- package/dist/archive-merge.js +157 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +25 -5
- package/dist/archive-policy.d.ts +1 -1
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +1 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +3 -2
- package/dist/archive-staging.d.ts +13 -8
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +44 -192
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +9 -5
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-zip-admission.d.ts +2 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +2 -2
- package/dist/archive-zip-directory.d.ts +8 -1
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +6 -2
- package/dist/archive-zip-entry.d.ts +1 -1
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-names.d.ts +1 -1
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +1 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +17 -25
- package/dist/directory-guard.d.ts +17 -5
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +8 -6
- package/dist/directory-mode-node.d.ts +8 -0
- package/dist/directory-mode-node.d.ts.map +1 -0
- package/dist/directory-mode-node.js +93 -0
- package/dist/directory-mode-owner.d.ts +21 -0
- package/dist/directory-mode-owner.d.ts.map +1 -0
- package/dist/directory-mode-owner.js +73 -0
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -0
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +13 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +8 -11
- package/dist/filename.d.ts +6 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +36 -0
- package/dist/guarded-mkdir.d.ts +2 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +2 -2
- package/dist/guarded-mutation.d.ts +2 -2
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/json-document-store.d.ts +3 -0
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +2 -0
- package/dist/json-durable-queue-ownership.d.ts +3 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +7 -4
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +25 -28
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +18 -56
- package/dist/move-path-cleanup.d.ts +43 -0
- package/dist/move-path-cleanup.d.ts.map +1 -0
- package/dist/move-path-cleanup.js +159 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +30 -84
- package/dist/native-operations.d.ts +2 -4
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +7 -9
- package/dist/native-pinned-write-windows.d.ts +2 -2
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +5 -5
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +14 -4
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/opened-file-failure.d.ts +1 -0
- package/dist/opened-file-failure.d.ts.map +1 -1
- package/dist/opened-file-failure.js +4 -0
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +9 -3
- package/dist/pinned-write.d.ts +2 -2
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +32 -13
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +7 -3
- package/dist/regular-file.d.ts +1 -1
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +35 -6
- package/dist/replace-file-descriptor.d.ts +9 -1
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +58 -10
- package/dist/replace-file-temp-owner.d.ts +7 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +29 -0
- package/dist/replace-file.d.ts +1 -0
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +12 -38
- package/dist/root-context.d.ts +4 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +19 -0
- package/dist/root-impl.d.ts +43 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +13 -11
- package/dist/root-path-symlink.d.ts +6 -2
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +7 -6
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +6 -2
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +4 -0
- package/dist/root-write-verification.d.ts +3 -2
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +6 -0
- package/dist/secret-file.d.ts +9 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +136 -74
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +9 -3
- package/dist/sidecar-lock-acquire.d.ts +1 -0
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +24 -5
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +6 -4
- package/dist/sidecar-lock-types.d.ts +7 -0
- package/dist/sidecar-lock-types.d.ts.map +1 -1
- package/dist/sidecar-lock.d.ts +2 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +44 -3
- package/dist/write-open-flags.d.ts +5 -0
- package/dist/write-open-flags.d.ts.map +1 -0
- package/dist/write-open-flags.js +35 -0
- package/docs/advanced.md +1 -1
- package/docs/archive.md +56 -7
- package/docs/atomic.md +8 -2
- package/docs/contributing.md +13 -0
- package/docs/durability.md +3 -2
- package/docs/errors.md +3 -1
- package/docs/file-store.md +38 -8
- package/docs/local-roots.md +4 -3
- package/docs/output.md +4 -1
- package/docs/private-file-store.md +11 -2
- package/docs/reading.md +3 -1
- package/docs/regular-file.md +16 -2
- package/docs/root.md +4 -2
- package/docs/secret-file.md +37 -2
- package/docs/sidecar-lock.md +33 -5
- package/docs/store.md +5 -1
- package/docs/temp.md +19 -1
- package/docs/writing.md +24 -3
- package/package.json +14 -10
package/dist/secret-file.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
2
|
import fsp from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
|
|
5
5
|
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
6
6
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
7
|
-
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
7
|
+
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
|
|
8
|
+
import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
|
|
9
|
+
import { assertOwnedDirectory } from "./directory-mode-owner.js";
|
|
8
10
|
import { FsSafeError } from "./errors.js";
|
|
9
|
-
import { sameFileIdentity } from "./file-identity.js";
|
|
10
11
|
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
11
12
|
import { openPinnedFileSync } from "./pinned-open.js";
|
|
12
|
-
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
13
13
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
14
|
+
import { ensureTrailingSep } from "./root-context.js";
|
|
15
|
+
import { verifyAtomicWriteResult } from "./root-write-verification.js";
|
|
14
16
|
import { assertSecretFilePreview, DEFAULT_SECRET_FILE_MAX_BYTES, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
15
|
-
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
17
|
+
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
16
18
|
import { serializePathWrite } from "./write-queue.js";
|
|
17
19
|
export const PRIVATE_SECRET_DIR_MODE = 0o700;
|
|
18
20
|
export const PRIVATE_SECRET_FILE_MODE = 0o600;
|
|
@@ -104,95 +106,131 @@ function assertRealPathWithinRoot(rootDir, targetPath) {
|
|
|
104
106
|
throw new Error(`Private secret path must stay under ${rootDir}.`);
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
|
-
async function
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
async function createPrivateDirectory(directory, mode) {
|
|
110
|
+
try {
|
|
111
|
+
await fsp.mkdir(directory, { mode });
|
|
112
|
+
return true;
|
|
110
113
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
throw new Error(`Private secret ${kind} ${resolvedPath} has insecure permissions ${actualMode.toString(8)}.`);
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (error.code === "EEXIST")
|
|
116
|
+
return false;
|
|
117
|
+
throw error;
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
|
-
async function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (pathStat.isSymbolicLink() || !sameFileIdentity(pathStat, openedStat)) {
|
|
127
|
-
throw new FsSafeError("path-mismatch", "private secret path changed during write");
|
|
128
|
-
}
|
|
129
|
-
if (process.platform !== "win32") {
|
|
130
|
-
await handle.chmod(expectedMode);
|
|
131
|
-
const chmodStat = await handle.stat();
|
|
132
|
-
const actualMode = chmodStat.mode & 0o777;
|
|
133
|
-
if (actualMode !== expectedMode) {
|
|
134
|
-
throw new Error(`Private secret file ${resolvedPath} has insecure permissions ${actualMode.toString(8)}.`);
|
|
135
|
-
}
|
|
136
|
-
const refreshedPathStat = await fsp.lstat(resolvedPath);
|
|
137
|
-
if (refreshedPathStat.isSymbolicLink() || !sameFileIdentity(refreshedPathStat, chmodStat)) {
|
|
138
|
-
throw new FsSafeError("path-mismatch", "private secret path changed during mode check");
|
|
139
|
-
}
|
|
120
|
+
async function enforcePrivateDirectoryMode(params) {
|
|
121
|
+
if (process.platform === "win32")
|
|
122
|
+
return;
|
|
123
|
+
const stat = await inspectDirectoryIdentity(params.realPath, params.identity);
|
|
124
|
+
if (!params.created) {
|
|
125
|
+
const actualMode = Number(stat.mode & 4095n);
|
|
126
|
+
if (actualMode !== params.mode) {
|
|
127
|
+
throw new FsSafeError("insecure-permissions", `Private secret directory ${JSON.stringify(params.realPath)} has insecure permissions ${actualMode.toString(8)}.`);
|
|
140
128
|
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const ownerUid = process.geteuid?.();
|
|
132
|
+
if (ownerUid === undefined) {
|
|
133
|
+
throw new FsSafeError("helper-unavailable", "secret directory initialization requires owner identity");
|
|
134
|
+
}
|
|
135
|
+
const owner = await pinNodeDirectoryForMode(params.realPath, {
|
|
136
|
+
expectedIdentity: params.identity,
|
|
137
|
+
ownerUid,
|
|
138
|
+
});
|
|
139
|
+
try {
|
|
140
|
+
await owner.apply(params.mode, { beforeChmod: params.beforeChmod });
|
|
141
141
|
}
|
|
142
142
|
finally {
|
|
143
|
-
await
|
|
143
|
+
await owner.close();
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
+
async function inspectPrivateDirectory(directory, kind) {
|
|
147
|
+
return await inspectFileIdentity(async () => {
|
|
148
|
+
const stat = await fsp.lstat(directory, { bigint: true });
|
|
149
|
+
if (stat.isSymbolicLink()) {
|
|
150
|
+
throw new Error(`Private secret ${kind} ${directory} must not be a symlink.`);
|
|
151
|
+
}
|
|
152
|
+
if (!stat.isDirectory()) {
|
|
153
|
+
throw new Error(`Private secret ${kind} ${directory} must be a directory.`);
|
|
154
|
+
}
|
|
155
|
+
return stat;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
146
158
|
async function ensurePrivateDirectory(rootDir, targetDir, mode) {
|
|
147
159
|
const resolvedRoot = path.resolve(rootDir);
|
|
148
160
|
const resolvedTarget = path.resolve(targetDir);
|
|
149
|
-
await
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
161
|
+
let rootStat = await inspectPrivateDirectory(resolvedRoot, "root").catch((error) => {
|
|
162
|
+
if (error.code !== "ENOENT")
|
|
163
|
+
throw error;
|
|
164
|
+
return undefined;
|
|
165
|
+
});
|
|
166
|
+
let createdRoot = false;
|
|
167
|
+
if (!rootStat) {
|
|
168
|
+
try {
|
|
169
|
+
createdRoot = await createPrivateDirectory(resolvedRoot, mode);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (error.code !== "ENOENT")
|
|
173
|
+
throw error;
|
|
174
|
+
await fsp.mkdir(path.dirname(resolvedRoot), { recursive: true, mode });
|
|
175
|
+
createdRoot = await createPrivateDirectory(resolvedRoot, mode);
|
|
176
|
+
}
|
|
177
|
+
rootStat = await inspectPrivateDirectory(resolvedRoot, "root");
|
|
156
178
|
}
|
|
157
|
-
const rootGuard = await createAsyncDirectoryGuard(resolvedRoot);
|
|
158
|
-
|
|
179
|
+
const rootGuard = await createAsyncDirectoryGuard(resolvedRoot, { bigint: true });
|
|
180
|
+
assertOwnedDirectory(rootStat, rootGuard.stat);
|
|
181
|
+
await enforcePrivateDirectoryMode({
|
|
182
|
+
realPath: rootGuard.realPath, identity: rootStat, mode, created: createdRoot,
|
|
183
|
+
beforeChmod: () => assertAsyncDirectoryGuard(rootGuard),
|
|
184
|
+
});
|
|
159
185
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
160
186
|
if (resolvedTarget === resolvedRoot) {
|
|
161
|
-
return { rootGuard,
|
|
187
|
+
return { rootGuard, parentGuard: rootGuard };
|
|
162
188
|
}
|
|
163
189
|
assertPathWithinRoot(resolvedRoot, resolvedTarget);
|
|
164
190
|
const resolvedRootReal = rootGuard.realPath;
|
|
165
191
|
let current = resolvedRoot;
|
|
192
|
+
let targetGuard = rootGuard;
|
|
166
193
|
for (const segment of path
|
|
167
194
|
.relative(resolvedRoot, resolvedTarget)
|
|
168
195
|
.split(path.sep)
|
|
169
196
|
.filter(Boolean)) {
|
|
170
197
|
current = path.join(current, segment);
|
|
171
|
-
const parentGuard =
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
const parentGuard = targetGuard;
|
|
199
|
+
let created = false;
|
|
200
|
+
let identity;
|
|
201
|
+
while (true) {
|
|
202
|
+
await assertAsyncDirectoryGuard(rootGuard);
|
|
203
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
204
|
+
try {
|
|
205
|
+
identity = await inspectPrivateDirectory(current, "directory component");
|
|
206
|
+
break;
|
|
180
207
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
213
|
+
created = await createPrivateDirectory(current, mode);
|
|
214
|
+
// EEXIST grants no initialization authority; both outcomes need fresh type checks.
|
|
185
215
|
}
|
|
186
|
-
await assertAsyncDirectoryGuard(parentGuard);
|
|
187
|
-
await fsp.mkdir(current, { mode });
|
|
188
216
|
}
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
217
|
+
const currentGuard = await createAsyncDirectoryGuard(current, { bigint: true });
|
|
218
|
+
assertOwnedDirectory(identity, currentGuard.stat);
|
|
219
|
+
assertRealPathWithinRoot(resolvedRootReal, currentGuard.realPath);
|
|
220
|
+
await enforcePrivateDirectoryMode({
|
|
221
|
+
realPath: currentGuard.realPath, identity, mode, created,
|
|
222
|
+
beforeChmod: async () => {
|
|
223
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
224
|
+
await assertAsyncDirectoryGuard(rootGuard);
|
|
225
|
+
await assertAsyncDirectoryGuard(currentGuard);
|
|
226
|
+
},
|
|
227
|
+
});
|
|
192
228
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
193
229
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
230
|
+
await assertAsyncDirectoryGuard(currentGuard);
|
|
231
|
+
targetGuard = currentGuard;
|
|
194
232
|
}
|
|
195
|
-
return { rootGuard,
|
|
233
|
+
return { rootGuard, parentGuard: targetGuard };
|
|
196
234
|
}
|
|
197
235
|
async function secretFileWriteQueueKey(filePath) {
|
|
198
236
|
try {
|
|
@@ -203,19 +241,29 @@ async function secretFileWriteQueueKey(filePath) {
|
|
|
203
241
|
return path.resolve(filePath);
|
|
204
242
|
}
|
|
205
243
|
}
|
|
206
|
-
|
|
244
|
+
// Internal preparation for private writers and their pre-write locks; not lasting authorization.
|
|
245
|
+
export async function prepareSecretFileWrite(params) {
|
|
207
246
|
const mode = params.mode ?? PRIVATE_SECRET_FILE_MODE;
|
|
208
247
|
const dirMode = params.dirMode ?? PRIVATE_SECRET_DIR_MODE;
|
|
209
248
|
const resolvedRoot = path.resolve(params.rootDir);
|
|
210
249
|
const resolvedFile = path.resolve(params.filePath);
|
|
211
250
|
assertPathWithinRoot(resolvedRoot, resolvedFile);
|
|
251
|
+
for (const [kind, value] of [["file", mode], ["directory", dirMode]]) {
|
|
252
|
+
if (!Number.isInteger(value) || value < 0 || value > 0o7777) {
|
|
253
|
+
throw new FsSafeError("invalid-path", `Private secret ${kind} mode must be an integer between 0o0000 and 0o7777.`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
212
256
|
const intendedParentDir = path.dirname(resolvedFile);
|
|
213
|
-
const { rootGuard,
|
|
257
|
+
const { rootGuard, parentGuard } = await ensurePrivateDirectory(resolvedRoot, intendedParentDir, dirMode);
|
|
214
258
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
215
|
-
|
|
216
|
-
|
|
259
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
260
|
+
assertRealPathWithinRoot(rootGuard.realPath, parentGuard.realPath);
|
|
217
261
|
const fileName = path.basename(resolvedFile);
|
|
218
|
-
const finalFilePath = path.join(
|
|
262
|
+
const finalFilePath = path.join(parentGuard.realPath, fileName);
|
|
263
|
+
return { mode, rootGuard, parentGuard, fileName, finalFilePath };
|
|
264
|
+
}
|
|
265
|
+
async function materializeSecretFileAtomic(params, createOnly) {
|
|
266
|
+
const { mode, rootGuard, parentGuard, fileName, finalFilePath } = await prepareSecretFileWrite(params);
|
|
219
267
|
try {
|
|
220
268
|
const stat = await fsp.lstat(finalFilePath);
|
|
221
269
|
if (createOnly) {
|
|
@@ -235,7 +283,7 @@ async function materializeSecretFileAtomic(params, createOnly) {
|
|
|
235
283
|
}
|
|
236
284
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
237
285
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
238
|
-
|
|
286
|
+
await runPinnedWriteHelper({
|
|
239
287
|
rootPath: parentGuard.realPath,
|
|
240
288
|
relativeParentPath: "",
|
|
241
289
|
basename: fileName,
|
|
@@ -244,10 +292,24 @@ async function materializeSecretFileAtomic(params, createOnly) {
|
|
|
244
292
|
overwrite: !createOnly,
|
|
245
293
|
input: { kind: "buffer", data: typeof params.content === "string" ? params.content : Buffer.from(params.content) },
|
|
246
294
|
rootIdentity: { dev: parentGuard.stat.dev, ino: parentGuard.stat.ino },
|
|
295
|
+
verifyPublished: async (fd, expectedIdentity, publishedParentGuard) => {
|
|
296
|
+
await assertAsyncDirectoryGuard(rootGuard);
|
|
297
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
298
|
+
await verifyAtomicWriteResult({
|
|
299
|
+
root: {
|
|
300
|
+
rootDir: rootGuard.dir,
|
|
301
|
+
rootReal: rootGuard.realPath,
|
|
302
|
+
rootWithSep: ensureTrailingSep(rootGuard.realPath),
|
|
303
|
+
rootIdentity: { dev: rootGuard.stat.dev, ino: rootGuard.stat.ino },
|
|
304
|
+
},
|
|
305
|
+
targetPath: finalFilePath,
|
|
306
|
+
fd,
|
|
307
|
+
expectedIdentity,
|
|
308
|
+
expectedMode: mode,
|
|
309
|
+
parentGuard: publishedParentGuard,
|
|
310
|
+
});
|
|
311
|
+
},
|
|
247
312
|
});
|
|
248
|
-
await assertAsyncDirectoryGuard(rootGuard);
|
|
249
|
-
await assertAsyncDirectoryGuard(parentGuard);
|
|
250
|
-
await enforcePrivateFileIdentityAndMode(finalFilePath, identity, mode);
|
|
251
313
|
}
|
|
252
314
|
export async function writeSecretFileAtomic(params) {
|
|
253
315
|
const canonicalPath = await secretFileWriteQueueKey(params.filePath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAoBxC;
|
|
1
|
+
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAoBxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0ChB"}
|
package/dist/sibling-temp.js
CHANGED
|
@@ -2,7 +2,7 @@ import crypto, { randomUUID } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
5
|
-
import { sanitizeUntrustedFileName } from "./filename.js";
|
|
5
|
+
import { fitFileNameToPortableComponent, sanitizeUntrustedFileName } from "./filename.js";
|
|
6
6
|
import { applyDirectoryMode } from "./replace-file-descriptor.js";
|
|
7
7
|
import { root } from "./root.js";
|
|
8
8
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
@@ -42,8 +42,14 @@ function buildSiblingTempPath(params) {
|
|
|
42
42
|
const safePrefix = assertSafePathPrefix(params.tempPrefix, {
|
|
43
43
|
label: "sibling temp prefix",
|
|
44
44
|
});
|
|
45
|
-
const
|
|
46
|
-
|
|
45
|
+
const prefix = `${safePrefix}${id}-`;
|
|
46
|
+
const suffix = ".part";
|
|
47
|
+
const safeTail = fitFileNameToPortableComponent({
|
|
48
|
+
prefix,
|
|
49
|
+
fileName: sanitizeUntrustedFileName(path.basename(params.targetPath), params.fallbackFileName),
|
|
50
|
+
suffix,
|
|
51
|
+
});
|
|
52
|
+
return path.join(path.dirname(params.targetPath), `${prefix}${safeTail}${suffix}`);
|
|
47
53
|
}
|
|
48
54
|
export async function writeViaSiblingTempPath(params) {
|
|
49
55
|
const rootDir = await fs
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAS3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAqT5B"}
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
5
|
+
import { fileObservation } from "./file-observation.js";
|
|
4
6
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
5
7
|
import { openSidecarRoot } from "./sidecar-lock-root.js";
|
|
6
8
|
import { createNativeExclusiveFile } from "./native-operations.js";
|
|
7
9
|
import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
8
10
|
import { readSidecarLockRawSnapshot, parseSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
|
|
9
11
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
10
|
-
async function resolveNormalizedTargetPath(targetPath) {
|
|
12
|
+
async function resolveNormalizedTargetPath(targetPath, lockRoot) {
|
|
11
13
|
const resolved = path.resolve(targetPath);
|
|
12
14
|
const dir = path.dirname(resolved);
|
|
15
|
+
if (lockRoot) {
|
|
16
|
+
// The target is an arbitration key, not necessarily inside the lock Root.
|
|
17
|
+
// Leave parent creation to the Root-backed sidecar write.
|
|
18
|
+
await lockRoot.resolve(".");
|
|
19
|
+
const parent = await canonicalPathFromExistingAncestor(dir);
|
|
20
|
+
await lockRoot.resolve(".");
|
|
21
|
+
return path.join(parent, path.basename(resolved));
|
|
22
|
+
}
|
|
13
23
|
await fs.mkdir(dir, { recursive: true });
|
|
14
24
|
try {
|
|
15
25
|
return path.join(await fs.realpath(dir), path.basename(resolved));
|
|
@@ -23,7 +33,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
23
33
|
validateSidecarLockRetryOptions(retry);
|
|
24
34
|
validateSidecarLockTimeoutMs(options.timeoutMs);
|
|
25
35
|
context.ensureExitCleanupRegistered();
|
|
26
|
-
const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
|
|
36
|
+
const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath, options.lockRoot);
|
|
27
37
|
const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
|
|
28
38
|
let held = context.held.get(normalizedTargetPath);
|
|
29
39
|
if (held &&
|
|
@@ -43,11 +53,15 @@ export async function acquireSidecarLock(options, context) {
|
|
|
43
53
|
held.reentrantOwner !== undefined &&
|
|
44
54
|
options.reentrantOwner === held.reentrantOwner) {
|
|
45
55
|
held.refCount += 1;
|
|
56
|
+
// Retention is monotonic: any same-owner request to keep the sidecar on
|
|
57
|
+
// exit upgrades the held lock; a later default acquisition never revokes it.
|
|
58
|
+
if (options.retainOnExit === true) {
|
|
59
|
+
held.retainOnExit = true;
|
|
60
|
+
}
|
|
46
61
|
return context.handleForHeldLock(normalizedTargetPath, held);
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
64
|
const startedAt = Date.now();
|
|
50
|
-
const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
|
|
51
65
|
const reclaimGuardPath = `${lockPath}.reclaim`;
|
|
52
66
|
let ownsReclaimGuard = false;
|
|
53
67
|
let attempt = 0;
|
|
@@ -59,7 +73,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
59
73
|
if ((options.timeoutMs !== undefined &&
|
|
60
74
|
options.timeoutMs !== Number.POSITIVE_INFINITY &&
|
|
61
75
|
elapsed >= options.timeoutMs) ||
|
|
62
|
-
(
|
|
76
|
+
(retry.retries !== undefined && attempt >= retry.retries)) {
|
|
63
77
|
throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
|
|
64
78
|
code: "file_lock_timeout",
|
|
65
79
|
lockPath,
|
|
@@ -101,10 +115,14 @@ export async function acquireSidecarLock(options, context) {
|
|
|
101
115
|
if (options.lockRoot) {
|
|
102
116
|
const lockRoot = options.lockRoot;
|
|
103
117
|
const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
|
|
118
|
+
const observation = fileObservation();
|
|
104
119
|
try {
|
|
105
|
-
await lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
|
|
120
|
+
await observation.run(() => lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 }));
|
|
106
121
|
}
|
|
107
122
|
catch (error) {
|
|
123
|
+
// Only this invocation's failed exclusive open grants denial retry authority.
|
|
124
|
+
lockFileCreateDenied = observation.has(error, `exclusive-create:${lockPath}`) &&
|
|
125
|
+
isTransientLockFileDenial(error, lockPath);
|
|
108
126
|
if (error instanceof FsSafeError && error.code === "already-exists") {
|
|
109
127
|
throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
|
|
110
128
|
}
|
|
@@ -157,6 +175,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
157
175
|
acquiredAt: Date.now(),
|
|
158
176
|
metadata: options.metadata ?? {},
|
|
159
177
|
lockRoot: options.lockRoot,
|
|
178
|
+
retainOnExit: options.retainOnExit,
|
|
160
179
|
parsePayload: options.parsePayload,
|
|
161
180
|
};
|
|
162
181
|
context.held.set(normalizedTargetPath, createdHeld);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-reclaim.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-reclaim.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAO7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAa3C,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,KAAK,sBAAsB,GAAG,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAErF,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,EACvC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GAChC,mBAAmB,GAAG,IAAI,CAE5B;AAYD,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE7E;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,CAAC;CACxB,
|
|
1
|
+
{"version":3,"file":"sidecar-lock-reclaim.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-reclaim.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,10 +30,12 @@ export function readSidecarLockOwnershipToken(raw) {
|
|
|
30
30
|
}
|
|
31
31
|
export function serializeSidecarLockPayload(payload) {
|
|
32
32
|
const ownershipToken = createSidecarLockOwnershipToken();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const raw = `${JSON.stringify(payload, null, 2)}\n${ownershipToken}\n`;
|
|
34
|
+
const bytes = Buffer.byteLength(raw, "utf8");
|
|
35
|
+
if (bytes > MAX_LOCK_PAYLOAD_BYTES) {
|
|
36
|
+
throw new FsSafeError("too-large", `sidecar lock payload exceeds limit of ${MAX_LOCK_PAYLOAD_BYTES} bytes (got ${bytes})`);
|
|
37
|
+
}
|
|
38
|
+
return { raw, ownershipToken };
|
|
37
39
|
}
|
|
38
40
|
export function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
39
41
|
const resolved = path.resolve(lockPath);
|
|
@@ -33,6 +33,13 @@ export type SidecarLockAcquireOptions<TPayload extends Record<string, unknown>>
|
|
|
33
33
|
metadata?: Record<string, unknown>;
|
|
34
34
|
parsePayload?: (raw: string) => unknown;
|
|
35
35
|
lockRoot?: Root;
|
|
36
|
+
/**
|
|
37
|
+
* Keep the lock file when the process exits naturally. Default `false`:
|
|
38
|
+
* process-exit handlers release held locks. Set only for deliberately
|
|
39
|
+
* retained ownership records (for example, fail-closed build locks) whose
|
|
40
|
+
* liveness is governed by the caller's own stale policy.
|
|
41
|
+
*/
|
|
42
|
+
retainOnExit?: boolean;
|
|
36
43
|
onCompromised?: (info: SidecarLockCompromisedInfo) => void;
|
|
37
44
|
compromiseCheckIntervalMs?: number;
|
|
38
45
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-types.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAE1E,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,aAAa,GAAG,qBAAqB,CAAC;AAE7E,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,yBAAyB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,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,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5C,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,OAAO,CAAC;KAC5B,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,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,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,YAAY,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,sBAAsB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CACjF,yBAAyB,CAAC,QAAQ,CAAC,EACnC,YAAY,CACb,GAAG;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC"}
|
|
1
|
+
{"version":3,"file":"sidecar-lock-types.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-types.ts"],"names":[],"mappings":"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"}
|
package/dist/sidecar-lock.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { SidecarLockAcquireOptions, SidecarLockHandle, SidecarLockHeldEntry, WithSidecarLockOptions } from "./sidecar-lock-types.js";
|
|
2
2
|
export type { SidecarLockStaleSnapshot } from "./sidecar-lock-reclaim.js";
|
|
3
3
|
export type { SidecarLockAcquireOptions, SidecarLockCompromisedInfo, SidecarLockHandle, SidecarLockHeldEntry, SidecarLockRetryOptions, SidecarLockStaleRecovery, WithSidecarLockOptions, } from "./sidecar-lock-types.js";
|
|
4
|
+
/** True when a retain-unaware package copy registered the process-exit handlers first. */
|
|
5
|
+
export declare function exitCleanupCannotRetain(): boolean;
|
|
4
6
|
export declare function createSidecarLockManager(key: string): {
|
|
5
7
|
acquire: <TPayload extends Record<string, unknown>>(options: SidecarLockAcquireOptions<TPayload>) => Promise<SidecarLockHandle>;
|
|
6
8
|
withLock: <T, TPayload extends Record<string, unknown>>(options: SidecarLockAcquireOptions<TPayload>, fn: () => Promise<T>) => Promise<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAqIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAuFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
|
package/dist/sidecar-lock.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
2
3
|
import { sameFileIdentity } from "./file-identity.js";
|
|
3
4
|
import { removeSidecarLockIfUnchanged, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
4
5
|
import { acquireSidecarLock } from "./sidecar-lock-acquire.js";
|
|
@@ -7,6 +8,11 @@ import { createSuppressedError } from "./suppressed-error.js";
|
|
|
7
8
|
const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
|
|
8
9
|
const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
|
|
9
10
|
const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
|
|
11
|
+
const GLOBAL_BEFORE_EXIT_KEY = Symbol.for("fsSafe.sidecarLockBeforeExitCleanup");
|
|
12
|
+
// Set by copies whose exit handlers honor retainOnExit. When an older package
|
|
13
|
+
// copy registered the handlers first, this marker stays absent and retained
|
|
14
|
+
// acquisitions must fail closed rather than silently lose the guarantee.
|
|
15
|
+
const GLOBAL_RETAIN_AWARE_KEY = Symbol.for("fsSafe.sidecarLockRetainAwareCleanup");
|
|
10
16
|
function getGlobalManagers() {
|
|
11
17
|
const globalWithState = globalThis;
|
|
12
18
|
if (!globalWithState[GLOBAL_STATE_KEY]) {
|
|
@@ -89,11 +95,12 @@ function releaseAllReclaimGuardsSync(state) {
|
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
|
-
function releaseAllLocksSync(state) {
|
|
98
|
+
function releaseAllLocksSync(state, options) {
|
|
93
99
|
for (const [normalizedTargetPath, held] of state.held) {
|
|
94
100
|
void held.handle.close().catch(() => undefined);
|
|
95
101
|
try {
|
|
96
|
-
|
|
102
|
+
const retained = options?.preserveRetained === true && held.retainOnExit;
|
|
103
|
+
if (!retained && !held.lockRoot && snapshotMatchesSync(held.lockPath, held.snapshot)) {
|
|
97
104
|
fsSync.rmSync(held.lockPath, { force: true });
|
|
98
105
|
}
|
|
99
106
|
}
|
|
@@ -109,14 +116,43 @@ function ensureGlobalExitCleanupRegistered() {
|
|
|
109
116
|
if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
|
|
110
117
|
return;
|
|
111
118
|
globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
|
|
119
|
+
globalWithCleanup[GLOBAL_RETAIN_AWARE_KEY] = true;
|
|
112
120
|
const cleanup = () => {
|
|
113
121
|
for (const state of getGlobalManagers().values()) {
|
|
114
|
-
releaseAllLocksSync(state);
|
|
122
|
+
releaseAllLocksSync(state, { preserveRetained: true });
|
|
115
123
|
}
|
|
116
124
|
};
|
|
117
125
|
globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
|
|
118
126
|
process.on("exit", cleanup);
|
|
119
127
|
}
|
|
128
|
+
/** True when a retain-unaware package copy registered the process-exit handlers first. */
|
|
129
|
+
export function exitCleanupCannotRetain() {
|
|
130
|
+
const globalWithCleanup = globalThis;
|
|
131
|
+
return globalWithCleanup[GLOBAL_CLEANUP_KEY] === true && globalWithCleanup[GLOBAL_RETAIN_AWARE_KEY] !== true;
|
|
132
|
+
}
|
|
133
|
+
function ensureGlobalBeforeExitCleanupRegistered() {
|
|
134
|
+
const globalWithCleanup = globalThis;
|
|
135
|
+
if (globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY]) {
|
|
136
|
+
return globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY];
|
|
137
|
+
}
|
|
138
|
+
// Independent of the exit registration, which an older package copy may own.
|
|
139
|
+
const lifecycle = { armed: false };
|
|
140
|
+
globalWithCleanup[GLOBAL_BEFORE_EXIT_KEY] = lifecycle;
|
|
141
|
+
process.on("beforeExit", () => {
|
|
142
|
+
if (!lifecycle.armed)
|
|
143
|
+
return;
|
|
144
|
+
// Cleanup itself schedules I/O; retry only after another acquisition.
|
|
145
|
+
lifecycle.armed = false;
|
|
146
|
+
for (const state of getGlobalManagers().values()) {
|
|
147
|
+
for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
|
|
148
|
+
if (held.lockRoot && !held.retainOnExit) {
|
|
149
|
+
void releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return lifecycle;
|
|
155
|
+
}
|
|
120
156
|
async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
|
|
121
157
|
const current = state.held.get(normalizedTargetPath);
|
|
122
158
|
if (current !== held) {
|
|
@@ -158,6 +194,7 @@ async function releaseHeldLock(state, normalizedTargetPath, held, options = {})
|
|
|
158
194
|
}
|
|
159
195
|
}
|
|
160
196
|
function handleForHeldLock(state, normalizedTargetPath, held) {
|
|
197
|
+
ensureGlobalBeforeExitCleanupRegistered().armed = true;
|
|
161
198
|
return createHeldSidecarLockHandle({
|
|
162
199
|
normalizedTargetPath,
|
|
163
200
|
held,
|
|
@@ -170,8 +207,12 @@ export function createSidecarLockManager(key) {
|
|
|
170
207
|
state.cleanupRegistered = true;
|
|
171
208
|
state.reclaimCleanupRegistered = true;
|
|
172
209
|
ensureGlobalExitCleanupRegistered();
|
|
210
|
+
ensureGlobalBeforeExitCleanupRegistered().armed = true;
|
|
173
211
|
}
|
|
174
212
|
async function acquire(options) {
|
|
213
|
+
if (options.retainOnExit === true && exitCleanupCannotRetain()) {
|
|
214
|
+
throw new FsSafeError("helper-unavailable", "retainOnExit requires this process's exit handlers to be retain-aware; an older package copy registered them first");
|
|
215
|
+
}
|
|
175
216
|
return await acquireSidecarLock(options, {
|
|
176
217
|
held: state.held,
|
|
177
218
|
reclaimGuards: state.reclaimGuards,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
export declare function resolveNonblockingWriteFlag(constants?: Partial<Pick<typeof fsSync.constants, "O_NONBLOCK">>): number;
|
|
3
|
+
export declare function isNonRegularWriteOpenError(error: unknown, filePath: string, flags: number): Promise<boolean>;
|
|
4
|
+
export declare function isNonRegularWriteOpenErrorSync(error: unknown, filePath: string, flags: number): boolean;
|
|
5
|
+
//# sourceMappingURL=write-open-flags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-open-flags.d.ts","sourceRoot":"","sources":["../src/write-open-flags.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAI7B,wBAAgB,2BAA2B,CACzC,SAAS,GAAE,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAoB,GACjF,MAAM,CAIR;AAUD,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAOT"}
|