@openclaw/fs-safe 0.16.0 → 0.17.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 +48 -0
- package/README.md +8 -1
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +2 -8
- package/dist/advanced.d.ts +2 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +22 -23
- package/dist/archive-merge.d.ts +1 -0
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +4 -4
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +1 -0
- package/dist/archive-options.d.ts +2 -0
- package/dist/archive-options.d.ts.map +1 -1
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-zip-count.d.ts.map +1 -1
- package/dist/archive-zip-count.js +21 -1
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +23 -1
- package/dist/archive-zip-loader.d.ts +2 -0
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +7 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +7 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -3
- package/dist/byte-view.d.ts +3 -0
- package/dist/byte-view.d.ts.map +1 -0
- package/dist/byte-view.js +13 -0
- package/dist/creation-darwin.d.ts +0 -1
- package/dist/creation-darwin.d.ts.map +1 -1
- package/dist/creation-darwin.js +0 -9
- package/dist/directory-durability.d.ts +6 -6
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-receipt.d.ts +2 -2
- package/dist/directory-receipt.d.ts.map +1 -1
- package/dist/directory-receipt.js +15 -19
- package/dist/file-cleanup.d.ts +1 -0
- package/dist/file-cleanup.d.ts.map +1 -1
- package/dist/file-cleanup.js +7 -4
- package/dist/file-contents.d.ts +6 -0
- package/dist/file-contents.d.ts.map +1 -0
- package/dist/file-contents.js +40 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +16 -4
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
- package/dist/file-lock-sync-root-acquire.js +3 -0
- package/dist/file-lock-sync-root-held.d.ts +1 -2
- package/dist/file-lock-sync-root-held.d.ts.map +1 -1
- package/dist/file-lock-sync-root-held.js +7 -5
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
- package/dist/file-lock-sync-stale-admission.js +3 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -11
- package/dist/file-store.js +3 -3
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +6 -27
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +2 -5
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +2 -6
- package/dist/json-durable-queue-paths.d.ts.map +1 -1
- package/dist/json-durable-queue-paths.js +2 -24
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +10 -9
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +32 -75
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +19 -21
- package/dist/move-path-cleanup.d.ts +5 -19
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +57 -21
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +62 -39
- package/dist/native-staged-file.d.ts +2 -1
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +8 -5
- package/dist/native.js +2 -2
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +11 -2
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +2 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -17
- package/dist/pinned-write-input.d.ts.map +1 -1
- package/dist/pinned-write-input.js +11 -1
- package/dist/pinned-write-mode.d.ts +3 -3
- package/dist/pinned-write-mode.d.ts.map +1 -1
- package/dist/pinned-write-mode.js +15 -8
- package/dist/pinned-write-staged.d.ts.map +1 -1
- package/dist/pinned-write-staged.js +10 -11
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +17 -13
- package/dist/publish-file.d.ts +2 -2
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +56 -96
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +35 -44
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +28 -26
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +13 -22
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +10 -16
- package/dist/replace-file-temp-owner.js +6 -6
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +9 -13
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +20 -3
- package/dist/root-file-final-admission.d.ts +1 -1
- package/dist/root-file-final-admission.d.ts.map +1 -1
- package/dist/root-file-final-admission.js +5 -2
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +3 -2
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +50 -20
- package/dist/root-move-noreplace.d.ts +2 -0
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +2 -2
- package/dist/root-read-admission.d.ts.map +1 -1
- package/dist/root-read-admission.js +7 -2
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +15 -1
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +4 -6
- package/dist/sidecar-lock-handle.d.ts +3 -0
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +6 -0
- package/dist/sidecar-lock-reclaim.d.ts +1 -1
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +11 -8
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +3 -5
- package/dist/staged-directory.d.ts +2 -2
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/strict-file-identity.d.ts +1 -1
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +9 -9
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +2 -27
- package/dist/temp-workspace-owner.js +4 -4
- package/dist/unicode-path.d.ts.map +1 -1
- package/dist/unicode-path.js +3 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +4 -2
- package/dist/write-file-handle.d.ts +7 -0
- package/dist/write-file-handle.d.ts.map +1 -1
- package/dist/write-file-handle.js +23 -0
- package/dist/write-open-flags.d.ts.map +1 -1
- package/dist/write-open-flags.js +1 -8
- package/dist/write-queue.d.ts.map +1 -1
- package/dist/write-queue.js +1 -4
- package/docs/advanced.md +68 -1
- package/docs/archive.md +41 -2
- package/docs/atomic.md +29 -5
- package/docs/contributing.md +4 -0
- package/docs/creation.md +8 -4
- package/docs/durability.md +35 -0
- package/docs/file-contents.md +68 -0
- package/docs/json.md +5 -4
- package/docs/local-roots.md +2 -0
- package/docs/mutation-policy-proof.md +5 -3
- package/docs/native.md +9 -8
- package/docs/path.md +4 -4
- package/docs/public-api.md +5 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +2 -2
- package/docs/regular-file.md +3 -0
- package/docs/root.md +9 -0
- package/docs/sidecar-lock.md +9 -1
- package/docs/staged-file.md +4 -3
- package/docs/store.md +3 -1
- package/docs/temp.md +4 -1
- package/docs/types.md +18 -2
- package/docs/walk.md +7 -0
- package/docs/writing.md +9 -2
- package/package.json +8 -8
package/dist/move-path.js
CHANGED
|
@@ -2,10 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import fsSync, { constants as fsConstants } from "node:fs";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { assertSyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { guardedRename } from "./guarded-mutation.js";
|
|
8
|
-
import { assertSourceStillMatches, cleanupCopiedEntry, createCleanupCopiedEntryState, entryIdentity, sameIdentity, sourceChangedError, } from "./move-path-cleanup.js";
|
|
8
|
+
import { assertSourceStillMatches, cleanupCopiedEntry, createCleanupCopiedEntryState, entryIdentity, inspectSourceDirectory, inspectSourceEntry, sameIdentity, sourceChangedError, } from "./move-path-cleanup.js";
|
|
9
9
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
10
10
|
import { realpathSync } from "./realpath.js";
|
|
11
11
|
import { cleanupPinnedFilePath } from "./file-cleanup.js";
|
|
@@ -57,7 +57,7 @@ function isSameOrDescendant(parentPath, candidatePath) {
|
|
|
57
57
|
(!path.isAbsolute(relative) && !relative.startsWith(`..${path.sep}`) && relative !== ".."));
|
|
58
58
|
}
|
|
59
59
|
async function assertCopyDestinationOutsideSource(sourcePath, targetPath, expectedIdentity) {
|
|
60
|
-
const sourceStat = fsSync.lstatSync(sourcePath);
|
|
60
|
+
const sourceStat = inspectSourceEntry(sourcePath, () => fsSync.lstatSync(sourcePath, { bigint: true }));
|
|
61
61
|
const sourceIdentity = entryIdentity(sourceStat);
|
|
62
62
|
if (expectedIdentity && !sameIdentity(expectedIdentity, sourceIdentity)) {
|
|
63
63
|
throw sourceChangedError(sourcePath);
|
|
@@ -80,7 +80,7 @@ async function assertCopyDestinationOutsideSource(sourcePath, targetPath, expect
|
|
|
80
80
|
return sourceIdentity;
|
|
81
81
|
}
|
|
82
82
|
function modeBits(mode) {
|
|
83
|
-
return mode &
|
|
83
|
+
return Number(mode & 511n);
|
|
84
84
|
}
|
|
85
85
|
async function chmodDirectoryPinned(directoryPath, mode) {
|
|
86
86
|
if (process.platform === "win32") {
|
|
@@ -120,21 +120,25 @@ async function copyRegularFilePinned(params) {
|
|
|
120
120
|
throw error;
|
|
121
121
|
}
|
|
122
122
|
try {
|
|
123
|
-
const
|
|
123
|
+
const inspectOpenedSource = () => inspectSourceEntry(params.from, () => {
|
|
124
|
+
const stat = fsSync.fstatSync(sourceHandle.fd, { bigint: true });
|
|
125
|
+
if (params.rejectHardlinks && stat.nlink > 1n)
|
|
126
|
+
throw hardlinkedSourceError(params.from);
|
|
127
|
+
if (!stat.isFile())
|
|
128
|
+
throw sourceChangedError(params.from);
|
|
129
|
+
return stat;
|
|
130
|
+
});
|
|
131
|
+
const openedStat = inspectOpenedSource();
|
|
124
132
|
openedIdentity = entryIdentity(openedStat);
|
|
125
|
-
if (params.rejectHardlinks && openedStat.nlink > 1) {
|
|
126
|
-
throw hardlinkedSourceError(params.from);
|
|
127
|
-
}
|
|
128
133
|
const openAdvancedWindowsCtime = process.platform === "win32" &&
|
|
129
134
|
params.identity.dev === openedIdentity.dev &&
|
|
130
135
|
params.identity.ino === openedIdentity.ino &&
|
|
131
136
|
params.identity.mode === openedIdentity.mode &&
|
|
132
137
|
params.identity.nlink === openedIdentity.nlink &&
|
|
133
138
|
params.identity.size === openedIdentity.size &&
|
|
134
|
-
params.identity.
|
|
135
|
-
openedIdentity.
|
|
136
|
-
if (!
|
|
137
|
-
(!sameIdentity(params.identity, openedIdentity) && !openAdvancedWindowsCtime)) {
|
|
139
|
+
params.identity.mtimeNs === openedIdentity.mtimeNs &&
|
|
140
|
+
openedIdentity.ctimeNs >= params.identity.ctimeNs;
|
|
141
|
+
if (!sameIdentity(params.identity, openedIdentity) && !openAdvancedWindowsCtime) {
|
|
138
142
|
throw sourceChangedError(params.from);
|
|
139
143
|
}
|
|
140
144
|
await assertSourceStillMatches(params.from, openedIdentity);
|
|
@@ -154,11 +158,7 @@ async function copyRegularFilePinned(params) {
|
|
|
154
158
|
}
|
|
155
159
|
// Re-check the opened source before the staged tree can be committed. If
|
|
156
160
|
// it changed while we copied, the caller should retry the move.
|
|
157
|
-
|
|
158
|
-
if (params.rejectHardlinks && finalSourceStat.nlink > 1) {
|
|
159
|
-
throw hardlinkedSourceError(params.from);
|
|
160
|
-
}
|
|
161
|
-
if (!sameIdentity(openedIdentity, entryIdentity(finalSourceStat))) {
|
|
161
|
+
if (!sameIdentity(openedIdentity, inspectOpenedSource())) {
|
|
162
162
|
throw sourceChangedError(params.from);
|
|
163
163
|
}
|
|
164
164
|
await destinationHandle.chmod(modeBits(params.mode));
|
|
@@ -178,21 +178,8 @@ async function copyRegularFilePinned(params) {
|
|
|
178
178
|
}
|
|
179
179
|
return openedIdentity;
|
|
180
180
|
}
|
|
181
|
-
function inspectSourceDirectory(sourcePath, expected) {
|
|
182
|
-
try {
|
|
183
|
-
return inspectDirectoryIdentitySync(sourcePath, expected);
|
|
184
|
-
}
|
|
185
|
-
catch (error) {
|
|
186
|
-
const code = error?.code;
|
|
187
|
-
if (code === "ENOENT" || code === "ENOTDIR" ||
|
|
188
|
-
(error instanceof FsSafeError && (error.code === "path-mismatch" || error.code === "not-file"))) {
|
|
189
|
-
throw sourceChangedError(sourcePath);
|
|
190
|
-
}
|
|
191
|
-
throw error;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
181
|
async function copyEntryWithManifest(from, to, options, expectedIdentity) {
|
|
195
|
-
const sourceStat = fsSync.lstatSync(from);
|
|
182
|
+
const sourceStat = inspectSourceEntry(from, () => fsSync.lstatSync(from, { bigint: true }));
|
|
196
183
|
const identity = entryIdentity(sourceStat);
|
|
197
184
|
if (expectedIdentity && !sameIdentity(expectedIdentity, identity)) {
|
|
198
185
|
throw sourceChangedError(from);
|
|
@@ -208,8 +195,7 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
|
|
|
208
195
|
return { ...identity, kind: "leaf" };
|
|
209
196
|
}
|
|
210
197
|
if (sourceStat.isDirectory()) {
|
|
211
|
-
const
|
|
212
|
-
const directoryIdentity = Object.freeze({ dev: directoryStat.dev, ino: directoryStat.ino });
|
|
198
|
+
const directoryIdentity = Object.freeze({ dev: sourceStat.dev, ino: sourceStat.ino });
|
|
213
199
|
await fs.mkdir(to, { mode: modeBits(sourceStat.mode) || 0o755 });
|
|
214
200
|
options.onCreated?.(fsSync.lstatSync(to, { bigint: true }));
|
|
215
201
|
const children = [];
|
|
@@ -226,6 +212,7 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
|
|
|
226
212
|
name: child,
|
|
227
213
|
manifest: await copyEntryWithManifest(path.join(from, child), path.join(to, child), {
|
|
228
214
|
sourceHardlinks: options.sourceHardlinks, budget: options.budget,
|
|
215
|
+
aliases: options.aliases,
|
|
229
216
|
}),
|
|
230
217
|
});
|
|
231
218
|
}
|
|
@@ -241,9 +228,13 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
|
|
|
241
228
|
if (!sourceStat.isFile()) {
|
|
242
229
|
throw new Error(`Refusing to move non-file path with copy fallback: ${from}`);
|
|
243
230
|
}
|
|
244
|
-
if (options.sourceHardlinks === "reject" && sourceStat.nlink >
|
|
231
|
+
if (options.sourceHardlinks === "reject" && sourceStat.nlink > 1n) {
|
|
245
232
|
throw hardlinkedSourceError(from);
|
|
246
233
|
}
|
|
234
|
+
const aliasKey = options.aliases && identity.nlink > 1n ? `${identity.dev}:${identity.ino}` : undefined;
|
|
235
|
+
const alias = aliasKey ? options.aliases.get(aliasKey) : undefined;
|
|
236
|
+
if (alias && !sameIdentity(alias, identity))
|
|
237
|
+
throw sourceChangedError(from);
|
|
247
238
|
const copiedIdentity = await copyRegularFilePinned({
|
|
248
239
|
from,
|
|
249
240
|
identity,
|
|
@@ -252,7 +243,14 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
|
|
|
252
243
|
to,
|
|
253
244
|
onCreated: options.onCreated,
|
|
254
245
|
});
|
|
255
|
-
|
|
246
|
+
// A permitted Windows open advances every name's ctime. Share its receipt
|
|
247
|
+
// only after the preceding fingerprint and this complete copy were verified.
|
|
248
|
+
if (alias)
|
|
249
|
+
return Object.assign(alias, copiedIdentity);
|
|
250
|
+
const manifest = { ...copiedIdentity, kind: "leaf" };
|
|
251
|
+
if (aliasKey)
|
|
252
|
+
options.aliases.set(aliasKey, manifest);
|
|
253
|
+
return manifest;
|
|
256
254
|
}
|
|
257
255
|
function assertSynchronousResult(returned, name) {
|
|
258
256
|
if (returned !== undefined) {
|
|
@@ -275,13 +273,13 @@ export async function movePathWithCopyFallback(options) {
|
|
|
275
273
|
const callerPublished = options.onDestinationPublished;
|
|
276
274
|
let assertionRejected = false;
|
|
277
275
|
let destinationPublished = false;
|
|
278
|
-
const assertBeforeMutation = () => {
|
|
279
|
-
assertSynchronousResult(callerMutationAssert
|
|
276
|
+
const assertBeforeMutation = callerMutationAssert == null ? undefined : () => {
|
|
277
|
+
assertSynchronousResult(callerMutationAssert(), "assertBeforeMutation");
|
|
280
278
|
};
|
|
281
279
|
const assertBeforeRename = () => {
|
|
282
280
|
try {
|
|
283
281
|
assertSynchronousResult(callerRenameAssert?.(), "assertBeforeRename");
|
|
284
|
-
assertBeforeMutation();
|
|
282
|
+
assertBeforeMutation?.();
|
|
285
283
|
}
|
|
286
284
|
catch (error) {
|
|
287
285
|
assertionRejected = true;
|
|
@@ -332,6 +330,29 @@ export async function movePathWithCopyFallback(options) {
|
|
|
332
330
|
// never become the published target; the copy loop fences nlink again.
|
|
333
331
|
}
|
|
334
332
|
const sourceIdentity = await assertCopyDestinationOutsideSource(sourcePath, targetPath);
|
|
333
|
+
const sourceParentPath = path.dirname(sourcePath);
|
|
334
|
+
const sourceParent = assertBeforeMutation || callerRenameAssert || callerPublished
|
|
335
|
+
? await createAsyncDirectoryGuard(realpathSync.native(sourceParentPath), { bigint: true })
|
|
336
|
+
: undefined;
|
|
337
|
+
const assertSourceParent = sourceParent ? () => {
|
|
338
|
+
try {
|
|
339
|
+
if (realpathSync.native(sourceParentPath) !== sourceParent.realPath)
|
|
340
|
+
throw sourceChangedError(sourcePath);
|
|
341
|
+
assertSyncDirectoryGuard(sourceParent);
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
const code = error?.code;
|
|
345
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP" ||
|
|
346
|
+
(error instanceof FsSafeError && (error.code === "path-mismatch" || error.code === "not-file"))) {
|
|
347
|
+
throw sourceChangedError(sourcePath);
|
|
348
|
+
}
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
} : undefined;
|
|
352
|
+
const assertBeforeCleanup = assertSourceParent ? () => {
|
|
353
|
+
assertBeforeMutation?.();
|
|
354
|
+
assertSourceParent();
|
|
355
|
+
} : undefined;
|
|
335
356
|
const targetDir = path.dirname(targetPath);
|
|
336
357
|
const staged = path.join(targetDir, `.fs-safe-move-${process.pid}-${randomUUID()}.tmp`);
|
|
337
358
|
const stage = await createMoveStageOwner(staged);
|
|
@@ -339,6 +360,7 @@ export async function movePathWithCopyFallback(options) {
|
|
|
339
360
|
const manifest = await copyEntryWithManifest(sourcePath, staged, {
|
|
340
361
|
sourceHardlinks: rejectHardlinks ? "reject" : "allow",
|
|
341
362
|
onCreated: stage.record,
|
|
363
|
+
...(!rejectHardlinks && process.platform === "win32" ? { aliases: new Map() } : {}),
|
|
342
364
|
...(rejectHardlinks ? { budget: { discovered: 1 } } : {}),
|
|
343
365
|
}, sourceIdentity);
|
|
344
366
|
const cleanupState = createCleanupCopiedEntryState(sourcePath, manifest);
|
|
@@ -355,7 +377,8 @@ export async function movePathWithCopyFallback(options) {
|
|
|
355
377
|
onRenamed();
|
|
356
378
|
},
|
|
357
379
|
});
|
|
358
|
-
|
|
380
|
+
assertSourceParent?.();
|
|
381
|
+
const cleanupResult = await cleanupCopiedEntry(sourcePath, manifest, cleanupState, assertBeforeCleanup);
|
|
359
382
|
if (cleanupResult === "stale") {
|
|
360
383
|
throw sourceChangedError(sourcePath);
|
|
361
384
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DirectoryReceipt } from "./directory-durability.js";
|
|
2
|
+
import { type BigIntStats, type Stats } from "node:fs";
|
|
2
3
|
import type { AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
4
|
import type { FileIdentityStat } from "./file-identity.js";
|
|
4
5
|
import { type NativeBinding } from "./native-binding.js";
|
|
@@ -24,7 +25,7 @@ export declare const createNativeStage: (...args: Parameters<typeof NativeStaged
|
|
|
24
25
|
export declare const writeNativeStage: typeof NativeStagedFile.write;
|
|
25
26
|
export type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileFailureDetails, StagedFilePublication, StagedFileReceipt, } from "./staged-file-types.js";
|
|
26
27
|
export declare function stageFileInDirectory(options: {
|
|
27
|
-
directory: string | DirectoryReceipt
|
|
28
|
+
directory: string | DirectoryReceipt<Stats | BigIntStats>;
|
|
28
29
|
content: string | Uint8Array;
|
|
29
30
|
/** Published mode; the unpublished stage stays at 0600. Defaults to 0600. */
|
|
30
31
|
mode?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,12 +50,12 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import { assertDarwinCreationAcl
|
|
53
|
+
import { assertDarwinCreationAcl } from "./creation-darwin.js";
|
|
54
54
|
import { assertPrivateCreationFile } from "./creation-file-state.js";
|
|
55
55
|
import { requireNativeBinding } from "./native.js";
|
|
56
56
|
import { syncFileBestEffortSync } from "./file-sync.js";
|
|
57
57
|
import { randomUUID } from "node:crypto";
|
|
58
|
-
import fs from "node:fs";
|
|
58
|
+
import fs, {} from "node:fs";
|
|
59
59
|
import path from "node:path";
|
|
60
60
|
import { FsSafeError } from "./errors.js";
|
|
61
61
|
import { assertSynchronousCallbackResult, MutationAuthorityError } from "./mutation-authority.js";
|
|
@@ -262,9 +262,12 @@ class NativeStagedFile {
|
|
|
262
262
|
const fd = state.fileFd;
|
|
263
263
|
if (input.kind === "file")
|
|
264
264
|
assertNativeCopyCompleted(input, copied);
|
|
265
|
-
const assertBeforeChmod = this.#
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
const assertBeforeChmod = this.#assertBeforeMutation;
|
|
266
|
+
const beforeChmodResult = assertBeforeChmod?.();
|
|
267
|
+
if (this.#private) {
|
|
268
|
+
assertSynchronousCallbackResult(beforeChmodResult, "assertBeforeMutation");
|
|
269
|
+
assertPrivateCreationFile(fs.fstatSync(fd, { bigint: true }), fd);
|
|
270
|
+
}
|
|
268
271
|
fs.fchmodSync(fd, 0o600);
|
|
269
272
|
// Some filesystems report successful chmod without enforcing its mode.
|
|
270
273
|
if (this.#verifyMode)
|
package/dist/native.js
CHANGED
|
@@ -22,7 +22,7 @@ function isMuslFromReport() {
|
|
|
22
22
|
return true;
|
|
23
23
|
}
|
|
24
24
|
catch {
|
|
25
|
-
// Continue with
|
|
25
|
+
// Continue with ELF and filesystem inspection.
|
|
26
26
|
}
|
|
27
27
|
return undefined;
|
|
28
28
|
}
|
|
@@ -114,7 +114,7 @@ function isMuslFromElfInterpreter() {
|
|
|
114
114
|
function isMusl() {
|
|
115
115
|
if (process.platform !== "linux")
|
|
116
116
|
return false;
|
|
117
|
-
for (const detector of [isMuslFromReport,
|
|
117
|
+
for (const detector of [isMuslFromReport, isMuslFromElfInterpreter, isMuslFromFilesystem]) {
|
|
118
118
|
const result = detector();
|
|
119
119
|
if (result !== undefined)
|
|
120
120
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACxC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AACpD,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,WAAW,CAAA;CAAE,CAAC,CAAC"}
|
package/dist/opened-realpath.js
CHANGED
|
@@ -5,9 +5,18 @@ import { openedPathResolutionError } from "./opened-file-failure.js";
|
|
|
5
5
|
import { sameFileIdentity } from "./file-identity.js";
|
|
6
6
|
import { isNotFoundPathError } from "./path.js";
|
|
7
7
|
import { realpathSync } from "./realpath.js";
|
|
8
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
8
9
|
export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
9
|
-
const
|
|
10
|
-
|
|
10
|
+
const fd = handle.fd;
|
|
11
|
+
const identity = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
|
|
12
|
+
const resolved = await resolveOpenedFileRealPathForFd(fd, identity, ioPath);
|
|
13
|
+
let first = resolved.stat;
|
|
14
|
+
inspectFileIdentitySync(() => {
|
|
15
|
+
const current = first ?? fsSync.statSync(resolved.realPath, { bigint: true });
|
|
16
|
+
first = undefined;
|
|
17
|
+
return current;
|
|
18
|
+
}, identity);
|
|
19
|
+
return resolved.realPath;
|
|
11
20
|
}
|
|
12
21
|
export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
13
22
|
const statOptions = typeof handleStat.dev === "bigint" || typeof handleStat.ino === "bigint"
|
package/dist/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAczB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAM1B,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWvE;AAaD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAI1E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAA6B,GAAG,IAAI,CAIjG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CA0ClE;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAa/F;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAChE,OAAO,CAsBT;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,IAAI,CAMhE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BpE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAczB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAM1B,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWvE;AAaD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAI1E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAA6B,GAAG,IAAI,CAIjG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CA0ClE;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAa/F;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAChE,OAAO,CAsBT;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,IAAI,CAMhE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BpE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CASrF"}
|
package/dist/path.js
CHANGED
|
@@ -160,7 +160,8 @@ export function splitSafeRelativePath(relativePath) {
|
|
|
160
160
|
export function resolveSafeRelativePath(rootDir, relativePath) {
|
|
161
161
|
assertNoWindowsPathAlias(rootDir, "filesystem", "root dir uses a Windows filesystem namespace alias");
|
|
162
162
|
const root = resolvePathPreservingWindowsRoot(rootDir);
|
|
163
|
-
|
|
163
|
+
splitSafeRelativePath(relativePath);
|
|
164
|
+
const target = resolvePathFromBasePreservingWindowsRoot(root, relativePath);
|
|
164
165
|
if (!isPathInside(root, target)) {
|
|
165
166
|
throw new FsSafeError("outside-workspace", "relative path escapes root");
|
|
166
167
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,0BAA0B,CAAC;AAKlC,YAAY,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC;IAC5C,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,sFAAsF;IACtF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA8BF,wBAAsB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAK1E;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
|
package/dist/permissions.js
CHANGED
|
@@ -39,23 +39,9 @@ export async function inspectPathPermissions(targetPath, opts) {
|
|
|
39
39
|
const admissionPlatform = process.platform === "win32"
|
|
40
40
|
? process.platform
|
|
41
41
|
: (opts?.platform ?? process.platform);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
ok: false,
|
|
46
|
-
isSymlink: false,
|
|
47
|
-
isDir: false,
|
|
48
|
-
mode: null,
|
|
49
|
-
bits: null,
|
|
50
|
-
source: "unknown",
|
|
51
|
-
worldWritable: false,
|
|
52
|
-
groupWritable: false,
|
|
53
|
-
worldReadable: false,
|
|
54
|
-
groupReadable: false,
|
|
55
|
-
error: st.error,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
const st = await safeStatAdmitted(targetPath);
|
|
42
|
+
const st = hasWindowsPathAlias(targetPath, "filesystem", admissionPlatform)
|
|
43
|
+
? failedSafeStat("Path uses a Windows filesystem namespace alias")
|
|
44
|
+
: await safeStatAdmitted(targetPath);
|
|
59
45
|
if (!st.ok) {
|
|
60
46
|
return {
|
|
61
47
|
ok: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-write-input.d.ts","sourceRoot":"","sources":["../src/pinned-write-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"pinned-write-input.d.ts","sourceRoot":"","sources":["../src/pinned-write-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAWhE,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import { types } from "node:util";
|
|
2
|
+
import { snapshotByteView } from "./byte-view.js";
|
|
1
3
|
import { writeCopyFileToFd } from "./copy-file-input.js";
|
|
2
4
|
import { FsSafeError } from "./errors.js";
|
|
3
5
|
import { writeAllToFile } from "./write-file-handle.js";
|
|
6
|
+
const isUint8Array = types.isUint8Array;
|
|
7
|
+
function streamChunkBytes(chunk) {
|
|
8
|
+
if (Buffer.isBuffer(chunk))
|
|
9
|
+
return chunk;
|
|
10
|
+
if (!isUint8Array(chunk))
|
|
11
|
+
return Buffer.from(chunk);
|
|
12
|
+
return snapshotByteView(chunk);
|
|
13
|
+
}
|
|
4
14
|
export async function writePinnedInput(target, input, maxBytes, assertBeforeMutation) {
|
|
5
15
|
if (input.kind === "file") {
|
|
6
16
|
await writeCopyFileToFd(typeof target === "number" ? target : target.fd, input, maxBytes, assertBeforeMutation);
|
|
@@ -19,7 +29,7 @@ export async function writePinnedInput(target, input, maxBytes, assertBeforeMuta
|
|
|
19
29
|
}
|
|
20
30
|
else {
|
|
21
31
|
for await (const chunk of input.stream) {
|
|
22
|
-
await write(
|
|
32
|
+
await write(streamChunkBytes(chunk));
|
|
23
33
|
}
|
|
24
34
|
}
|
|
25
35
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FileHandle } from "node:fs/promises";
|
|
2
|
-
export declare function assertPinnedWriteMode(fd: number, mode: number): void;
|
|
3
|
-
export declare function preparePinnedWriteMode(handle: FileHandle, createdMode: number, assertBeforeMutation?: () => void): Promise<(() => void) | undefined>;
|
|
4
|
-
export declare function pinnedWriteModeAssertion(fd: number, mode: number, assertBeforeMutation?: () => void): () => void;
|
|
2
|
+
export declare function assertPinnedWriteMode(fd: number, mode: number, privateCreation?: boolean): void;
|
|
3
|
+
export declare function preparePinnedWriteMode(handle: FileHandle, createdMode: number, assertBeforeMutation?: () => void, privateCreation?: boolean): Promise<(() => void) | undefined>;
|
|
4
|
+
export declare function pinnedWriteModeAssertion(fd: number, mode: number, assertBeforeMutation?: () => void, privateCreation?: boolean): () => void;
|
|
5
5
|
//# sourceMappingURL=pinned-write-mode.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-write-mode.d.ts","sourceRoot":"","sources":["../src/pinned-write-mode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"pinned-write-mode.d.ts","sourceRoot":"","sources":["../src/pinned-write-mode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,UAAQ,GAAG,IAAI,CAO7F;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,eAAe,UAAQ,GACtB,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,CAQnC;AAED,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,eAAe,UAAQ,GACtB,MAAM,IAAI,CAKZ"}
|
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
3
|
import { assertSynchronousCallbackResult } from "./mutation-authority.js";
|
|
4
|
-
|
|
4
|
+
import { assertPrivateCreationFile } from "./creation-file-state.js";
|
|
5
|
+
export function assertPinnedWriteMode(fd, mode, privateCreation = false) {
|
|
5
6
|
if (process.platform === "win32")
|
|
6
7
|
return;
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
const stat = fs.fstatSync(fd, { bigint: true });
|
|
9
|
+
if (privateCreation)
|
|
10
|
+
assertPrivateCreationFile(stat, fd);
|
|
11
|
+
if (Number(stat.mode & 4095n) !== mode) {
|
|
12
|
+
throw new FsSafeError("insecure-permissions", "filesystem did not enforce the requested file mode");
|
|
9
13
|
}
|
|
10
14
|
}
|
|
11
|
-
export async function preparePinnedWriteMode(handle, createdMode, assertBeforeMutation) {
|
|
15
|
+
export async function preparePinnedWriteMode(handle, createdMode, assertBeforeMutation, privateCreation = false) {
|
|
12
16
|
if (process.platform === "win32")
|
|
13
17
|
return assertBeforeMutation;
|
|
14
18
|
assertSynchronousCallbackResult(assertBeforeMutation?.(), "assertBeforeMutation");
|
|
15
|
-
if (createdMode !== 0o600)
|
|
19
|
+
if (createdMode !== 0o600) {
|
|
20
|
+
if (privateCreation)
|
|
21
|
+
assertPrivateCreationFile(fs.fstatSync(handle.fd, { bigint: true }), handle.fd);
|
|
16
22
|
await handle.chmod(0o600);
|
|
17
|
-
|
|
23
|
+
}
|
|
24
|
+
return pinnedWriteModeAssertion(handle.fd, 0o600, assertBeforeMutation, privateCreation);
|
|
18
25
|
}
|
|
19
|
-
export function pinnedWriteModeAssertion(fd, mode, assertBeforeMutation) {
|
|
26
|
+
export function pinnedWriteModeAssertion(fd, mode, assertBeforeMutation, privateCreation = false) {
|
|
20
27
|
return () => {
|
|
21
28
|
assertSynchronousCallbackResult(assertBeforeMutation?.(), "assertBeforeMutation");
|
|
22
|
-
assertPinnedWriteMode(fd, mode);
|
|
29
|
+
assertPinnedWriteMode(fd, mode, privateCreation);
|
|
23
30
|
};
|
|
24
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-write-staged.d.ts","sourceRoot":"","sources":["../src/pinned-write-staged.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAMnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"pinned-write-staged.d.ts","sourceRoot":"","sources":["../src/pinned-write-staged.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAMnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAKxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AASjE,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,GAC5C,OAAO,CAAC,gBAAgB,CAAC,CAqJ3B"}
|
|
@@ -9,7 +9,6 @@ import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
|
9
9
|
import { FsSafeError } from "./errors.js";
|
|
10
10
|
import { sameFileIdentity, sha256Hex } from "./file-identity.js";
|
|
11
11
|
import { syncFileBestEffort } from "./file-sync.js";
|
|
12
|
-
import { assertDarwinCreationAcl, privateFileMutationAssertion } from "./creation-darwin.js";
|
|
13
12
|
import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
|
|
14
13
|
import { writePinnedInput } from "./pinned-write-input.js";
|
|
15
14
|
import { assertPinnedWriteMode, pinnedWriteModeAssertion, preparePinnedWriteMode } from "./pinned-write-mode.js";
|
|
@@ -20,7 +19,8 @@ import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
|
20
19
|
import { createStagedFileReceipt, settleStagedFile } from "./staged-file-settlement.js";
|
|
21
20
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
22
21
|
export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
|
|
23
|
-
const verifyPosixMode =
|
|
22
|
+
const verifyPosixMode = process.platform !== "win32" &&
|
|
23
|
+
(params.verifyPosixMode === true || params.private === true);
|
|
24
24
|
const targetPath = path.join(parentPath, params.basename);
|
|
25
25
|
// Private staging must not consume the destination basename's filename budget.
|
|
26
26
|
const tempPath = path.join(parentPath, `.fs-safe-${randomUUID()}.tmp`);
|
|
@@ -49,18 +49,15 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
|
|
|
49
49
|
: await fs.open(tempPath, tempFlags, verifyPosixMode ? 0o600 : params.mode);
|
|
50
50
|
let verificationIdentity = fsSync.fstatSync(handle.fd, { bigint: true });
|
|
51
51
|
tempIdentity = verificationIdentity;
|
|
52
|
-
let assertBeforeMutation = params.
|
|
53
|
-
? privateFileMutationAssertion(handle.fd, params.assertBeforeMutation) : params.assertBeforeMutation;
|
|
52
|
+
let assertBeforeMutation = params.assertBeforeMutation;
|
|
54
53
|
const assertBeforeWrite = verifyPosixMode
|
|
55
|
-
? await preparePinnedWriteMode(handle, Number(tempIdentity.mode & 4095n), assertBeforeMutation)
|
|
54
|
+
? await preparePinnedWriteMode(handle, Number(tempIdentity.mode & 4095n), assertBeforeMutation, params.private)
|
|
56
55
|
: assertBeforeMutation;
|
|
57
56
|
if (verifyPosixMode)
|
|
58
|
-
assertPinnedWriteMode(handle.fd, 0o600);
|
|
57
|
+
assertPinnedWriteMode(handle.fd, 0o600, params.private);
|
|
59
58
|
await writePinnedInput(handle, params.input, params.maxBytes, assertBeforeWrite);
|
|
60
59
|
if (verifyPosixMode)
|
|
61
|
-
assertPinnedWriteMode(handle.fd, 0o600);
|
|
62
|
-
if (params.private)
|
|
63
|
-
assertDarwinCreationAcl(handle.fd);
|
|
60
|
+
assertPinnedWriteMode(handle.fd, 0o600, params.private);
|
|
64
61
|
tempStat = fsSync.fstatSync(handle.fd);
|
|
65
62
|
const tempPathStat = fsSync.lstatSync(tempPath);
|
|
66
63
|
if (tempPathStat.isSymbolicLink() || !sameFileIdentity(tempPathStat, tempStat)) {
|
|
@@ -69,8 +66,8 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
|
|
|
69
66
|
const expectedTempStat = tempStat;
|
|
70
67
|
await handle.chmod(params.mode);
|
|
71
68
|
if (verifyPosixMode) {
|
|
72
|
-
assertPinnedWriteMode(handle.fd, params.mode);
|
|
73
|
-
assertBeforeMutation = pinnedWriteModeAssertion(handle.fd, params.mode, assertBeforeMutation);
|
|
69
|
+
assertPinnedWriteMode(handle.fd, params.mode, params.private);
|
|
70
|
+
assertBeforeMutation = pinnedWriteModeAssertion(handle.fd, params.mode, params.assertBeforeMutation, params.private);
|
|
74
71
|
}
|
|
75
72
|
if (params.sync !== false) {
|
|
76
73
|
if (params.strictFileSync)
|
|
@@ -146,6 +143,8 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
|
|
|
146
143
|
}
|
|
147
144
|
});
|
|
148
145
|
await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
|
|
146
|
+
if (verifyPosixMode)
|
|
147
|
+
assertPinnedWriteMode((readHandle ?? handle).fd, params.mode, params.private);
|
|
149
148
|
return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
|
|
150
149
|
}
|
|
151
150
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAgB3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACvF,YAAY,EACV,gBAAgB,EAAE,oBAAoB,EAAE,sBAAsB,EAC9D,8BAA8B,EAAE,gCAAgC,EAChE,6BAA6B,EAAE,+BAA+B,EAC9D,4BAA4B,EAAE,iBAAiB,GAChD,MAAM,yBAAyB,CAAC;AAkDjC,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsD/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CAkB3B;AAED,wBAAsB,iCAAiC,CAAC,CAAC,EACvD,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,EAC5E,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAiBZ"}
|
package/dist/pinned-write.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { syncFileBestEffort } from "./file-sync.js";
|
|
2
|
-
import { assertDarwinCreationAcl, privateFileMutationAssertion } from "./creation-darwin.js";
|
|
3
2
|
import fsSync, {} from "node:fs";
|
|
4
3
|
import fs from "node:fs/promises";
|
|
5
4
|
import path from "node:path";
|
|
@@ -124,7 +123,8 @@ export async function withPinnedWriteRenameIdentityLock(params, run) {
|
|
|
124
123
|
}, run);
|
|
125
124
|
}
|
|
126
125
|
async function runPinnedWriteFallback(params) {
|
|
127
|
-
const verifyPosixMode =
|
|
126
|
+
const verifyPosixMode = process.platform !== "win32" &&
|
|
127
|
+
(params.verifyPosixMode === true || params.private === true);
|
|
128
128
|
const exactRoot = typeof params.rootIdentity?.dev === "bigint" && typeof params.rootIdentity.ino === "bigint"
|
|
129
129
|
? { dev: params.rootIdentity.dev, ino: params.rootIdentity.ino } : undefined;
|
|
130
130
|
const mutationAdmission = params.mutationAdmission;
|
|
@@ -250,25 +250,23 @@ async function runPinnedWriteFallback(params) {
|
|
|
250
250
|
},
|
|
251
251
|
});
|
|
252
252
|
let created = true;
|
|
253
|
+
let completed = false;
|
|
253
254
|
let createdIdentity;
|
|
254
255
|
try {
|
|
255
256
|
const verificationIdentity = fsSync.fstatSync(handle.fd, { bigint: true });
|
|
256
257
|
createdIdentity = verificationIdentity;
|
|
257
|
-
|
|
258
|
-
?
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
assertPinnedWriteMode(handle.fd, 0o600);
|
|
262
|
-
}
|
|
258
|
+
const assertBeforeWrite = verifyPosixMode
|
|
259
|
+
? await preparePinnedWriteMode(handle, Number(createdIdentity.mode & 4095n), assertBeforeMutation, params.private)
|
|
260
|
+
: assertBeforeMutation;
|
|
261
|
+
if (verifyPosixMode)
|
|
262
|
+
assertPinnedWriteMode(handle.fd, 0o600, params.private);
|
|
263
263
|
await writePinnedInput(handle, params.input, params.maxBytes, assertBeforeWrite);
|
|
264
264
|
if (verifyPosixMode)
|
|
265
|
-
assertPinnedWriteMode(handle.fd, 0o600);
|
|
266
|
-
if (params.private)
|
|
267
|
-
assertDarwinCreationAcl(handle.fd);
|
|
265
|
+
assertPinnedWriteMode(handle.fd, 0o600, params.private);
|
|
268
266
|
// Content writes may clear set-ID bits; finalize them through the owned fd.
|
|
269
267
|
await handle.chmod(params.mode);
|
|
270
268
|
if (verifyPosixMode)
|
|
271
|
-
assertPinnedWriteMode(handle.fd, params.mode);
|
|
269
|
+
assertPinnedWriteMode(handle.fd, params.mode, params.private);
|
|
272
270
|
if (params.sync !== false) {
|
|
273
271
|
if (params.strictFileSync)
|
|
274
272
|
await handle.sync();
|
|
@@ -281,6 +279,9 @@ async function runPinnedWriteFallback(params) {
|
|
|
281
279
|
// Publication is complete. A failed outer check must not remove its target.
|
|
282
280
|
created = false;
|
|
283
281
|
await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
|
|
282
|
+
if (verifyPosixMode)
|
|
283
|
+
assertPinnedWriteMode(handle.fd, params.mode, params.private);
|
|
284
|
+
completed = true;
|
|
284
285
|
return { dev: stat.dev, ino: stat.ino };
|
|
285
286
|
}
|
|
286
287
|
finally {
|
|
@@ -290,7 +291,10 @@ async function runPinnedWriteFallback(params) {
|
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
293
|
finally {
|
|
293
|
-
|
|
294
|
+
if (completed)
|
|
295
|
+
await handle.close();
|
|
296
|
+
else
|
|
297
|
+
await handle.close().catch(() => undefined);
|
|
294
298
|
}
|
|
295
299
|
}
|
|
296
300
|
}
|