@openclaw/fs-safe 0.6.0 → 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 +50 -0
- package/README.md +7 -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-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 +24 -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 +69 -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 +104 -35
- package/dist/archive-tar-runtime.d.ts +3 -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-names.js +2 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +38 -26
- 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-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +10 -5
- 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 +12 -3
- package/dist/native-binding.d.ts.map +1 -1
- 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/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +8 -9
- 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 +40 -26
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +6 -3
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +6 -5
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +17 -13
- 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/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/docs/advanced.md +8 -1
- package/docs/archive.md +194 -26
- package/docs/atomic.md +11 -2
- package/docs/durability.md +1 -1
- package/docs/file-store.md +2 -0
- package/docs/install.md +8 -4
- package/docs/json.md +8 -4
- package/docs/native-helper.md +15 -3
- package/docs/native.md +33 -6
- package/docs/output.md +10 -0
- 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 +2 -0
- package/docs/secret-file.md +5 -1
- package/docs/secure-file.md +4 -2
- package/docs/security-model.md +18 -0
- package/docs/sidecar-lock.md +4 -0
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- package/package.json +10 -10
|
@@ -4,11 +4,18 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileStore, fileStoreSync, } from "./file-store.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
|
-
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
8
7
|
import { isNotFoundPathError } from "./path.js";
|
|
9
8
|
import { throwFsSafeReadError } from "./read-error.js";
|
|
10
9
|
import { matchRootFileOpenFailure, openRootFileSync, } from "./root-file.js";
|
|
11
10
|
import { registerTempPathForExit, } from "./temp-cleanup.js";
|
|
11
|
+
import { TempWorkspaceCleanupCapability, TempWorkspaceCleanupOwner, } from "./temp-workspace-owner.js";
|
|
12
|
+
function resolveTempWorkspaceCleanupSafety(value) {
|
|
13
|
+
if (value === undefined || value === "compatible")
|
|
14
|
+
return "compatible";
|
|
15
|
+
if (value === "require-bounded")
|
|
16
|
+
return value;
|
|
17
|
+
throw new TypeError("cleanupSafety must be compatible or require-bounded");
|
|
18
|
+
}
|
|
12
19
|
function sanitizeTempPrefix(prefix) {
|
|
13
20
|
const sanitized = prefix.trim().replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
14
21
|
if (!sanitized || sanitized === "." || sanitized === "..") {
|
|
@@ -79,52 +86,47 @@ function ensurePrivateDirectorySync(dir, mode) {
|
|
|
79
86
|
// Best-effort on platforms that do not enforce POSIX modes.
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
|
-
async function cleanupWorkspace(dir, identity) {
|
|
83
|
-
let current;
|
|
84
|
-
try {
|
|
85
|
-
current = await fs.lstat(dir, { bigint: true });
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
return error.code === "ENOENT" ? "missing" : "identity-mismatch";
|
|
89
|
-
}
|
|
90
|
-
if (!sameFileIdentityForCleanup(current, identity)) {
|
|
91
|
-
return "identity-mismatch";
|
|
92
|
-
}
|
|
93
|
-
await fs.rm(dir, { recursive: true, force: true });
|
|
94
|
-
return "removed";
|
|
95
|
-
}
|
|
96
|
-
function cleanupWorkspaceSync(dir, identity) {
|
|
97
|
-
let current;
|
|
98
|
-
try {
|
|
99
|
-
current = fsSync.lstatSync(dir, { bigint: true });
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
return error.code === "ENOENT" ? "missing" : "identity-mismatch";
|
|
103
|
-
}
|
|
104
|
-
if (!sameFileIdentityForCleanup(current, identity)) {
|
|
105
|
-
return "identity-mismatch";
|
|
106
|
-
}
|
|
107
|
-
fsSync.rmSync(dir, { recursive: true, force: true });
|
|
108
|
-
return "removed";
|
|
109
|
-
}
|
|
110
89
|
async function createTempWorkspace(options) {
|
|
111
90
|
const dirMode = options.dirMode ?? 0o700;
|
|
112
91
|
const mode = options.mode ?? 0o600;
|
|
92
|
+
const cleanupSafety = resolveTempWorkspaceCleanupSafety(options.cleanupSafety);
|
|
113
93
|
const requestedRoot = path.resolve(options.rootDir);
|
|
114
94
|
const root = await fs.realpath(requestedRoot).catch(() => requestedRoot);
|
|
115
95
|
await ensurePrivateDirectory(root, dirMode);
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
96
|
+
const capability = new TempWorkspaceCleanupCapability(root, cleanupSafety);
|
|
97
|
+
let dir;
|
|
98
|
+
let stat;
|
|
99
|
+
let cleanupOwner;
|
|
100
|
+
let unregisterTempDir;
|
|
101
|
+
try {
|
|
102
|
+
dir = await fs.mkdtemp(path.join(root, sanitizeTempPrefix(options.prefix)));
|
|
103
|
+
await fs.chmod(dir, dirMode).catch(() => undefined);
|
|
104
|
+
stat = await fs.lstat(dir, { bigint: true });
|
|
105
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
106
|
+
throw new Error(`Temp workspace must be a directory: ${dir}`);
|
|
107
|
+
}
|
|
108
|
+
if (capability.parent)
|
|
109
|
+
capability.assertCurrent();
|
|
110
|
+
cleanupOwner = new TempWorkspaceCleanupOwner(dir, stat, capability);
|
|
111
|
+
unregisterTempDir = registerTempPathForExit(dir, {
|
|
112
|
+
cleanupSync: () => cleanupOwner.cleanupSync(),
|
|
113
|
+
});
|
|
121
114
|
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
try {
|
|
117
|
+
if (cleanupOwner)
|
|
118
|
+
cleanupOwner.cleanupSync();
|
|
119
|
+
else
|
|
120
|
+
capability.close();
|
|
121
|
+
}
|
|
122
|
+
catch (cleanupError) {
|
|
123
|
+
throw new AggregateError([error, cleanupError], "temp workspace creation and cleanup both failed");
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
const owner = cleanupOwner;
|
|
122
128
|
const identity = { dev: Number(stat.dev), ino: Number(stat.ino) };
|
|
123
|
-
|
|
124
|
-
const unregisterTempDir = registerTempPathForExit(dir, {
|
|
125
|
-
recursive: true,
|
|
126
|
-
identity: cleanupIdentity,
|
|
127
|
-
});
|
|
129
|
+
// Once registered, even a store-construction failure remains exit-cleanable.
|
|
128
130
|
const store = fileStore({ rootDir: dir, private: true, dirMode, mode });
|
|
129
131
|
return {
|
|
130
132
|
dir,
|
|
@@ -148,7 +150,7 @@ async function createTempWorkspace(options) {
|
|
|
148
150
|
},
|
|
149
151
|
cleanup: async () => {
|
|
150
152
|
try {
|
|
151
|
-
return await
|
|
153
|
+
return await owner.cleanup();
|
|
152
154
|
}
|
|
153
155
|
finally {
|
|
154
156
|
unregisterTempDir();
|
|
@@ -156,7 +158,7 @@ async function createTempWorkspace(options) {
|
|
|
156
158
|
},
|
|
157
159
|
[Symbol.asyncDispose]: async () => {
|
|
158
160
|
try {
|
|
159
|
-
await
|
|
161
|
+
await owner.cleanup();
|
|
160
162
|
}
|
|
161
163
|
finally {
|
|
162
164
|
unregisterTempDir();
|
|
@@ -182,6 +184,7 @@ export async function withTempWorkspace(options, run) {
|
|
|
182
184
|
export function tempWorkspaceSync(options) {
|
|
183
185
|
const dirMode = options.dirMode ?? 0o700;
|
|
184
186
|
const mode = options.mode ?? 0o600;
|
|
187
|
+
const cleanupSafety = resolveTempWorkspaceCleanupSafety(options.cleanupSafety);
|
|
185
188
|
const requestedRoot = path.resolve(options.rootDir);
|
|
186
189
|
let root = requestedRoot;
|
|
187
190
|
try {
|
|
@@ -191,23 +194,45 @@ export function tempWorkspaceSync(options) {
|
|
|
191
194
|
root = requestedRoot;
|
|
192
195
|
}
|
|
193
196
|
ensurePrivateDirectorySync(root, dirMode);
|
|
194
|
-
const
|
|
197
|
+
const capability = new TempWorkspaceCleanupCapability(root, cleanupSafety);
|
|
198
|
+
let dir;
|
|
199
|
+
let stat;
|
|
200
|
+
let cleanupOwner;
|
|
201
|
+
let unregisterTempDir;
|
|
195
202
|
try {
|
|
196
|
-
fsSync.
|
|
203
|
+
dir = fsSync.mkdtempSync(path.join(root, sanitizeTempPrefix(options.prefix)));
|
|
204
|
+
try {
|
|
205
|
+
fsSync.chmodSync(dir, dirMode);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Best-effort on platforms that do not enforce POSIX modes.
|
|
209
|
+
}
|
|
210
|
+
stat = fsSync.lstatSync(dir, { bigint: true });
|
|
211
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
212
|
+
throw new Error(`Temp workspace must be a directory: ${dir}`);
|
|
213
|
+
}
|
|
214
|
+
if (capability.parent)
|
|
215
|
+
capability.assertCurrent();
|
|
216
|
+
cleanupOwner = new TempWorkspaceCleanupOwner(dir, stat, capability);
|
|
217
|
+
unregisterTempDir = registerTempPathForExit(dir, {
|
|
218
|
+
cleanupSync: () => cleanupOwner.cleanupSync(),
|
|
219
|
+
});
|
|
197
220
|
}
|
|
198
|
-
catch {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
221
|
+
catch (error) {
|
|
222
|
+
try {
|
|
223
|
+
if (cleanupOwner)
|
|
224
|
+
cleanupOwner.cleanupSync();
|
|
225
|
+
else
|
|
226
|
+
capability.close();
|
|
227
|
+
}
|
|
228
|
+
catch (cleanupError) {
|
|
229
|
+
throw new AggregateError([error, cleanupError], "temp workspace creation and cleanup both failed");
|
|
230
|
+
}
|
|
231
|
+
throw error;
|
|
204
232
|
}
|
|
233
|
+
const owner = cleanupOwner;
|
|
205
234
|
const identity = { dev: Number(stat.dev), ino: Number(stat.ino) };
|
|
206
|
-
|
|
207
|
-
const unregisterTempDir = registerTempPathForExit(dir, {
|
|
208
|
-
recursive: true,
|
|
209
|
-
identity: cleanupIdentity,
|
|
210
|
-
});
|
|
235
|
+
// Once registered, even a store-construction failure remains exit-cleanable.
|
|
211
236
|
const store = fileStoreSync({ rootDir: dir, private: true, dirMode, mode });
|
|
212
237
|
return {
|
|
213
238
|
dir,
|
|
@@ -244,7 +269,7 @@ export function tempWorkspaceSync(options) {
|
|
|
244
269
|
},
|
|
245
270
|
cleanup: () => {
|
|
246
271
|
try {
|
|
247
|
-
return
|
|
272
|
+
return owner.cleanupSync();
|
|
248
273
|
}
|
|
249
274
|
finally {
|
|
250
275
|
unregisterTempDir();
|
|
@@ -252,7 +277,7 @@ export function tempWorkspaceSync(options) {
|
|
|
252
277
|
},
|
|
253
278
|
[Symbol.dispose]: () => {
|
|
254
279
|
try {
|
|
255
|
-
|
|
280
|
+
owner.cleanupSync();
|
|
256
281
|
}
|
|
257
282
|
finally {
|
|
258
283
|
unregisterTempDir();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAI/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAI/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GACpC,cAAc,GACd,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAkBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AAsND,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,gBAAgB,CAAC;IAC1C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAClC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4MtC"}
|
package/dist/publish-file.js
CHANGED
|
@@ -8,6 +8,7 @@ import { hashFileHandle } from "./file-hash.js";
|
|
|
8
8
|
import { sameFileIdentity, sameFileIdentityForCleanup, } from "./file-identity.js";
|
|
9
9
|
import { syncNativeFileBestEffort } from "./native-operations.js";
|
|
10
10
|
import { getNativeBinding, requireNativeBinding } from "./native.js";
|
|
11
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
11
12
|
import { directorySyncFailure, publicationFailure, rememberCreatedTarget, } from "./publish-file-failure.js";
|
|
12
13
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
13
14
|
const HARDLINK_FALLBACK_CODES = new Set([
|
|
@@ -29,10 +30,7 @@ export function isHardlinkFallbackError(error) {
|
|
|
29
30
|
return HARDLINK_FALLBACK_CODES.has(error?.code ?? "");
|
|
30
31
|
}
|
|
31
32
|
function sourceOpenFlags() {
|
|
32
|
-
return (
|
|
33
|
-
(process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
|
|
34
|
-
? fsSync.constants.O_NOFOLLOW
|
|
35
|
-
: 0));
|
|
33
|
+
return resolveReadOpenFlags();
|
|
36
34
|
}
|
|
37
35
|
function directoryOpenFlags() {
|
|
38
36
|
return (fsSync.constants.O_RDONLY |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-opened-file.d.ts","sourceRoot":"","sources":["../src/read-opened-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"read-opened-file.d.ts","sourceRoot":"","sources":["../src/read-opened-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,oBAAoB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,oBAAoB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,UAAU,CAAC,CAkBtB"}
|
package/dist/read-opened-file.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
1
2
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
2
3
|
import { FsSafeError } from "./errors.js";
|
|
3
4
|
export async function readOpenedFileSafely(params) {
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
6
|
+
if (maxBytes !== undefined && params.opened.stat.size > maxBytes) {
|
|
7
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got ${params.opened.stat.size})`);
|
|
6
8
|
}
|
|
7
|
-
const buffer =
|
|
9
|
+
const buffer = maxBytes === undefined
|
|
8
10
|
? await params.opened.handle.readFile()
|
|
9
|
-
: await readFileHandleBounded(params.opened.handle,
|
|
11
|
+
: await readFileHandleBounded(params.opened.handle, maxBytes);
|
|
10
12
|
return {
|
|
11
13
|
buffer,
|
|
12
14
|
containment: params.opened.containment,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,MAAM,MAAM,SAAS,CAAC;AAc7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CAQR;AAaD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CActF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAc3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CA6D3C;AA2DD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;CACb,CAoCA;AAmBD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwExF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAyE7E"}
|
package/dist/regular-file.js
CHANGED
|
@@ -2,12 +2,14 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { readFileDescriptorBoundedSync, readFileHandleBounded } from "./bounded-read.js";
|
|
5
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
5
6
|
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
6
7
|
import { FsSafeError } from "./errors.js";
|
|
7
|
-
import {
|
|
8
|
+
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
8
9
|
import { isNotFoundPathError } from "./path.js";
|
|
9
10
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
10
11
|
import { assertNoSymlinkParents, assertNoSymlinkParentsSync } from "./symlink-parents.js";
|
|
12
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
11
13
|
export function resolveRegularFileAppendFlags(constants = fsSync.constants) {
|
|
12
14
|
const noFollow = constants.O_NOFOLLOW;
|
|
13
15
|
return (constants.O_CREAT |
|
|
@@ -57,13 +59,15 @@ export function statRegularFileSync(filePath) {
|
|
|
57
59
|
return { missing: false, stat };
|
|
58
60
|
}
|
|
59
61
|
export async function readRegularFile(params) {
|
|
62
|
+
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
60
63
|
assertNoUnsafeDeviceReadPath(params.filePath);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
const before = await inspectFileIdentity(async () => {
|
|
65
|
+
const stat = await fs.lstat(params.filePath, { bigint: true });
|
|
66
|
+
assertRegularReadStat(stat, params.filePath, true);
|
|
67
|
+
return stat;
|
|
68
|
+
}).catch((error) => throwReadPreviewError(error, params.filePath));
|
|
69
|
+
if (maxBytes !== undefined && before.size > maxBytes) {
|
|
70
|
+
throw regularFileTooLargeError(params.filePath, maxBytes);
|
|
67
71
|
}
|
|
68
72
|
let handle;
|
|
69
73
|
try {
|
|
@@ -77,9 +81,17 @@ export async function readRegularFile(params) {
|
|
|
77
81
|
}
|
|
78
82
|
try {
|
|
79
83
|
const stat = await handle.stat();
|
|
80
|
-
|
|
84
|
+
const identity = await inspectFileIdentity(async () => {
|
|
85
|
+
const exact = await handle.stat({ bigint: true });
|
|
86
|
+
assertRegularReadStat(exact, params.filePath);
|
|
87
|
+
return exact;
|
|
88
|
+
}, before);
|
|
81
89
|
try {
|
|
82
|
-
|
|
90
|
+
await inspectFileIdentity(async () => {
|
|
91
|
+
const current = await fs.lstat(params.filePath, { bigint: true });
|
|
92
|
+
assertRegularReadStat(current, params.filePath);
|
|
93
|
+
return current;
|
|
94
|
+
}, identity);
|
|
83
95
|
}
|
|
84
96
|
catch (err) {
|
|
85
97
|
if (isNotFoundPathError(err)) {
|
|
@@ -87,27 +99,21 @@ export async function readRegularFile(params) {
|
|
|
87
99
|
}
|
|
88
100
|
throw err;
|
|
89
101
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
pathStat,
|
|
93
|
-
postOpenStat: stat,
|
|
94
|
-
preOpenStat: result.stat,
|
|
95
|
-
});
|
|
96
|
-
if (params.maxBytes !== undefined && stat.size > params.maxBytes) {
|
|
97
|
-
throw regularFileTooLargeError(params.filePath, params.maxBytes);
|
|
102
|
+
if (maxBytes !== undefined && stat.size > maxBytes) {
|
|
103
|
+
throw regularFileTooLargeError(params.filePath, maxBytes);
|
|
98
104
|
}
|
|
99
105
|
// With a byte cap, avoid readFile(): a raced file growth would allocate
|
|
100
106
|
// the oversized content before the post-read check could reject it.
|
|
101
107
|
let buffer;
|
|
102
108
|
try {
|
|
103
109
|
buffer =
|
|
104
|
-
|
|
110
|
+
maxBytes === undefined
|
|
105
111
|
? await handle.readFile()
|
|
106
|
-
: await readFileHandleBounded(handle,
|
|
112
|
+
: await readFileHandleBounded(handle, maxBytes);
|
|
107
113
|
}
|
|
108
114
|
catch (error) {
|
|
109
|
-
if (
|
|
110
|
-
translateBoundedReadOverflow(error, params.filePath,
|
|
115
|
+
if (maxBytes !== undefined) {
|
|
116
|
+
translateBoundedReadOverflow(error, params.filePath, maxBytes);
|
|
111
117
|
}
|
|
112
118
|
throw error;
|
|
113
119
|
}
|
|
@@ -117,20 +123,30 @@ export async function readRegularFile(params) {
|
|
|
117
123
|
await handle.close();
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
|
-
function
|
|
121
|
-
if (
|
|
122
|
-
throw new Error(`File
|
|
126
|
+
function throwReadPreviewError(error, filePath) {
|
|
127
|
+
if (isNotFoundPathError(error)) {
|
|
128
|
+
throw Object.assign(new Error(`File not found: ${filePath}`), { code: "ENOENT" });
|
|
123
129
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
function assertRegularReadStat(stat, filePath, preview = false) {
|
|
133
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
134
|
+
throw new Error(preview ? "path must be a regular file" : `File is not a regular file: ${filePath}`);
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
function readOpenedRegularFileSync(params) {
|
|
130
138
|
const stat = fsSync.fstatSync(params.fd);
|
|
131
|
-
|
|
139
|
+
const identity = inspectFileIdentitySync(() => {
|
|
140
|
+
const exact = fsSync.fstatSync(params.fd, { bigint: true });
|
|
141
|
+
assertRegularReadStat(exact, params.filePath);
|
|
142
|
+
return exact;
|
|
143
|
+
}, params.preOpenStat);
|
|
132
144
|
try {
|
|
133
|
-
|
|
145
|
+
inspectFileIdentitySync(() => {
|
|
146
|
+
const current = fsSync.lstatSync(params.filePath, { bigint: true });
|
|
147
|
+
assertRegularReadStat(current, params.filePath);
|
|
148
|
+
return current;
|
|
149
|
+
}, identity);
|
|
134
150
|
}
|
|
135
151
|
catch (error) {
|
|
136
152
|
if (isNotFoundPathError(error)) {
|
|
@@ -138,12 +154,6 @@ function readOpenedRegularFileSync(params) {
|
|
|
138
154
|
}
|
|
139
155
|
throw error;
|
|
140
156
|
}
|
|
141
|
-
verifyStableReadTarget({
|
|
142
|
-
filePath: params.filePath,
|
|
143
|
-
pathStat,
|
|
144
|
-
postOpenStat: stat,
|
|
145
|
-
preOpenStat: params.preOpenStat,
|
|
146
|
-
});
|
|
147
157
|
if (params.maxBytes !== undefined && stat.size > params.maxBytes) {
|
|
148
158
|
throw regularFileTooLargeError(params.filePath, params.maxBytes);
|
|
149
159
|
}
|
|
@@ -165,13 +175,21 @@ function readOpenedRegularFileSync(params) {
|
|
|
165
175
|
return { buffer, stat };
|
|
166
176
|
}
|
|
167
177
|
export function readRegularFileSync(params) {
|
|
178
|
+
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
168
179
|
assertNoUnsafeDeviceReadPath(params.filePath);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
let before;
|
|
181
|
+
try {
|
|
182
|
+
before = inspectFileIdentitySync(() => {
|
|
183
|
+
const stat = fsSync.lstatSync(params.filePath, { bigint: true });
|
|
184
|
+
assertRegularReadStat(stat, params.filePath, true);
|
|
185
|
+
return stat;
|
|
186
|
+
});
|
|
172
187
|
}
|
|
173
|
-
|
|
174
|
-
|
|
188
|
+
catch (error) {
|
|
189
|
+
throwReadPreviewError(error, params.filePath);
|
|
190
|
+
}
|
|
191
|
+
if (maxBytes !== undefined && before.size > maxBytes) {
|
|
192
|
+
throw regularFileTooLargeError(params.filePath, maxBytes);
|
|
175
193
|
}
|
|
176
194
|
let fd;
|
|
177
195
|
try {
|
|
@@ -187,25 +205,28 @@ export function readRegularFileSync(params) {
|
|
|
187
205
|
return readOpenedRegularFileSync({
|
|
188
206
|
fd,
|
|
189
207
|
filePath: params.filePath,
|
|
190
|
-
preOpenStat:
|
|
191
|
-
maxBytes
|
|
208
|
+
preOpenStat: before,
|
|
209
|
+
maxBytes,
|
|
192
210
|
});
|
|
193
211
|
}
|
|
194
212
|
finally {
|
|
195
213
|
fsSync.closeSync(fd);
|
|
196
214
|
}
|
|
197
215
|
}
|
|
198
|
-
function
|
|
199
|
-
if (!
|
|
200
|
-
throw new Error(`Refusing to append to non-file: ${
|
|
216
|
+
function assertRegularAppendStat(stat, filePath) {
|
|
217
|
+
if (!stat.isFile()) {
|
|
218
|
+
throw new Error(`Refusing to append to non-file: ${filePath}`);
|
|
201
219
|
}
|
|
202
|
-
if (
|
|
203
|
-
throw new Error(`Refusing to append to hardlinked file: ${
|
|
220
|
+
if (stat.nlink > 1n) {
|
|
221
|
+
throw new Error(`Refusing to append to hardlinked file: ${filePath}`);
|
|
204
222
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
}
|
|
224
|
+
function throwAppendIdentityError(error, filePath) {
|
|
225
|
+
if (isNotFoundPathError(error) ||
|
|
226
|
+
(error instanceof FsSafeError && error.code === "path-mismatch")) {
|
|
227
|
+
throw new Error(`Refusing to append after file changed: ${filePath}`, { cause: error });
|
|
208
228
|
}
|
|
229
|
+
throw error;
|
|
209
230
|
}
|
|
210
231
|
export async function appendRegularFile(options) {
|
|
211
232
|
if (options.rejectSymlinkParents === true) {
|
|
@@ -221,32 +242,49 @@ export async function appendRegularFile(options) {
|
|
|
221
242
|
}
|
|
222
243
|
let preOpenStat;
|
|
223
244
|
try {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
245
|
+
preOpenStat = await inspectFileIdentity(async () => {
|
|
246
|
+
const stat = await fs.lstat(options.filePath, { bigint: true });
|
|
247
|
+
if (stat.isSymbolicLink()) {
|
|
248
|
+
throw new Error(`Refusing to append through symlink: ${options.filePath}`);
|
|
249
|
+
}
|
|
250
|
+
if (!stat.isFile()) {
|
|
251
|
+
throw new Error(`Refusing to append to non-file: ${options.filePath}`);
|
|
252
|
+
}
|
|
253
|
+
return stat;
|
|
254
|
+
});
|
|
232
255
|
}
|
|
233
|
-
catch (
|
|
234
|
-
if (!isNotFoundPathError(
|
|
235
|
-
throw
|
|
236
|
-
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (!isNotFoundPathError(error))
|
|
258
|
+
throw error;
|
|
237
259
|
}
|
|
238
260
|
const contentBytes = Buffer.isBuffer(options.content)
|
|
239
261
|
? options.content.byteLength
|
|
240
262
|
: Buffer.byteLength(options.content, options.encoding ?? "utf8");
|
|
241
263
|
if (options.maxFileBytes !== undefined &&
|
|
242
|
-
(preOpenStat?.size ??
|
|
264
|
+
Number(preOpenStat?.size ?? 0n) + contentBytes > options.maxFileBytes) {
|
|
243
265
|
return;
|
|
244
266
|
}
|
|
267
|
+
await getFsSafeTestHooks()?.beforeRegularFileAppendOpen?.(options.filePath);
|
|
245
268
|
const handle = await fs.open(options.filePath, resolveRegularFileAppendFlags(), options.mode ?? 0o600);
|
|
246
269
|
try {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
270
|
+
let identity;
|
|
271
|
+
try {
|
|
272
|
+
identity = await inspectFileIdentity(async () => {
|
|
273
|
+
const stat = await handle.stat({ bigint: true });
|
|
274
|
+
assertRegularAppendStat(stat, options.filePath);
|
|
275
|
+
return stat;
|
|
276
|
+
}, preOpenStat);
|
|
277
|
+
await inspectFileIdentity(async () => {
|
|
278
|
+
const current = await fs.lstat(options.filePath, { bigint: true });
|
|
279
|
+
assertRegularAppendStat(current, options.filePath);
|
|
280
|
+
return current;
|
|
281
|
+
}, identity);
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
throwAppendIdentityError(error, options.filePath);
|
|
285
|
+
}
|
|
286
|
+
if (options.maxFileBytes !== undefined &&
|
|
287
|
+
Number(identity.size) + contentBytes > options.maxFileBytes) {
|
|
250
288
|
return;
|
|
251
289
|
}
|
|
252
290
|
await handle.chmod(options.mode ?? 0o600);
|
|
@@ -270,33 +308,49 @@ export function appendRegularFileSync(options) {
|
|
|
270
308
|
}
|
|
271
309
|
let preOpenStat;
|
|
272
310
|
try {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
311
|
+
preOpenStat = inspectFileIdentitySync(() => {
|
|
312
|
+
const stat = fsSync.lstatSync(options.filePath, { bigint: true });
|
|
313
|
+
if (stat.isSymbolicLink()) {
|
|
314
|
+
throw new Error(`Refusing to append through symlink: ${options.filePath}`);
|
|
315
|
+
}
|
|
316
|
+
if (!stat.isFile()) {
|
|
317
|
+
throw new Error(`Refusing to append to non-file: ${options.filePath}`);
|
|
318
|
+
}
|
|
319
|
+
return stat;
|
|
320
|
+
});
|
|
281
321
|
}
|
|
282
|
-
catch (
|
|
283
|
-
if (!isNotFoundPathError(
|
|
284
|
-
throw
|
|
285
|
-
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (!isNotFoundPathError(error))
|
|
324
|
+
throw error;
|
|
286
325
|
}
|
|
287
326
|
const contentBuffer = typeof options.content === "string"
|
|
288
327
|
? Buffer.from(options.content, options.encoding ?? "utf8")
|
|
289
328
|
: Buffer.from(options.content);
|
|
290
329
|
if (options.maxFileBytes !== undefined &&
|
|
291
|
-
(preOpenStat?.size ??
|
|
330
|
+
Number(preOpenStat?.size ?? 0n) + contentBuffer.byteLength > options.maxFileBytes) {
|
|
292
331
|
return;
|
|
293
332
|
}
|
|
333
|
+
getFsSafeTestHooks()?.beforeRegularFileAppendOpenSync?.(options.filePath);
|
|
294
334
|
const fd = fsSync.openSync(options.filePath, resolveRegularFileAppendFlags(), options.mode ?? 0o600);
|
|
295
335
|
try {
|
|
296
|
-
|
|
297
|
-
|
|
336
|
+
let identity;
|
|
337
|
+
try {
|
|
338
|
+
identity = inspectFileIdentitySync(() => {
|
|
339
|
+
const stat = fsSync.fstatSync(fd, { bigint: true });
|
|
340
|
+
assertRegularAppendStat(stat, options.filePath);
|
|
341
|
+
return stat;
|
|
342
|
+
}, preOpenStat);
|
|
343
|
+
inspectFileIdentitySync(() => {
|
|
344
|
+
const current = fsSync.lstatSync(options.filePath, { bigint: true });
|
|
345
|
+
assertRegularAppendStat(current, options.filePath);
|
|
346
|
+
return current;
|
|
347
|
+
}, identity);
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
throwAppendIdentityError(error, options.filePath);
|
|
351
|
+
}
|
|
298
352
|
if (options.maxFileBytes !== undefined &&
|
|
299
|
-
|
|
353
|
+
Number(identity.size) + contentBuffer.byteLength > options.maxFileBytes) {
|
|
300
354
|
return;
|
|
301
355
|
}
|
|
302
356
|
fsSync.fchmodSync(fd, options.mode ?? 0o600);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import syncFs from "node:fs";
|
|
1
|
+
import syncFs, { type BigIntStats } from "node:fs";
|
|
2
2
|
export type ReplaceFileDestinationHardlinkPolicy = "reject";
|
|
3
3
|
export type ReplaceFileCopyFallbackRestorePolicy = "restore-original" | "none";
|
|
4
4
|
export type ReplaceFileAtomicRestoreCleanup = "restored" | "restore-failed";
|
|
@@ -21,6 +21,7 @@ export declare function copyFallbackReplace(params: {
|
|
|
21
21
|
destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
|
|
22
22
|
restore: ReplaceFileCopyFallbackRestorePolicy;
|
|
23
23
|
maxRestoreBytes?: number;
|
|
24
|
+
expectedSourceIdentity?: BigIntStats;
|
|
24
25
|
sync: boolean;
|
|
25
26
|
}): Promise<void>;
|
|
26
27
|
export declare function copyFallbackReplaceSync(params: {
|
|
@@ -30,6 +31,7 @@ export declare function copyFallbackReplaceSync(params: {
|
|
|
30
31
|
destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
|
|
31
32
|
restore: ReplaceFileCopyFallbackRestorePolicy;
|
|
32
33
|
maxRestoreBytes?: number;
|
|
34
|
+
expectedSourceIdentity?: BigIntStats;
|
|
33
35
|
fchmodSync?: (fd: number, mode: number) => void;
|
|
34
36
|
sync: boolean;
|
|
35
37
|
}): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAO/D,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;AAuFF,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,IAAI,CAwBN;AAmID,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAwDhB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,IAAI,CAkEP"}
|