@openclaw/fs-safe 0.8.6 → 0.9.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 +15 -0
- package/dist/archive-durability.d.ts +24 -0
- package/dist/archive-durability.d.ts.map +1 -0
- package/dist/archive-durability.js +180 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +2 -1
- package/dist/archive-input.js +4 -4
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +3 -2
- package/dist/archive-merge.d.ts +1 -0
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +41 -8
- 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-read.d.ts.map +1 -1
- package/dist/archive-read.js +5 -4
- package/dist/archive-staging.d.ts +1 -1
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +24 -19
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -0
- package/dist/copy-publication.js +3 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +4 -4
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +5 -4
- package/dist/file-store-sync-write.d.ts +1 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +9 -6
- package/dist/file-store.d.ts +4 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +10 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +1 -2
- package/dist/guarded-mutation.js +1 -1
- package/dist/install-path.js +7 -7
- 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 +1 -0
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue-ownership.js +3 -2
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +18 -13
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +4 -3
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +31 -18
- package/dist/opened-file-failure.d.ts +1 -1
- package/dist/opened-file-failure.d.ts.map +1 -1
- package/dist/opened-file-failure.js +3 -2
- package/dist/permissions.js +3 -3
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +9 -3
- package/dist/publish-file.js +21 -21
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +30 -17
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -5
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +8 -3
- package/dist/root-impl.d.ts +1 -1
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +16 -2
- package/dist/secret-file.d.ts +1 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secret-read-async.js +5 -5
- package/dist/secure-file.d.ts +1 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +19 -9
- package/dist/sibling-staged-file.js +7 -7
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +16 -7
- package/dist/sidecar-lock-acquire.d.ts +1 -1
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +4 -3
- package/dist/sidecar-lock-policy.js +2 -2
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +16 -4
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -1
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +9 -4
- package/dist/walk.js +2 -2
- package/dist/write-open-flags.d.ts.map +1 -1
- package/dist/write-open-flags.js +1 -2
- package/docs/archive.md +35 -2
- package/docs/file-store.md +48 -3
- package/docs/json-store.md +10 -0
- package/docs/reading.md +6 -0
- package/docs/root.md +2 -2
- package/docs/secret-file.md +6 -0
- package/docs/sidecar-lock.md +2 -0
- package/docs/writing.md +5 -3
- package/package.json +11 -11
|
@@ -18,7 +18,7 @@ export async function unlinkBestEffort(filePath) {
|
|
|
18
18
|
}
|
|
19
19
|
export async function jsonDurableQueueEntryExists(filePath) {
|
|
20
20
|
try {
|
|
21
|
-
const stat =
|
|
21
|
+
const stat = fs.lstatSync(filePath);
|
|
22
22
|
return stat.isFile();
|
|
23
23
|
}
|
|
24
24
|
catch (error) {
|
|
@@ -30,7 +30,7 @@ export async function jsonDurableQueueEntryExists(filePath) {
|
|
|
30
30
|
}
|
|
31
31
|
async function unlinkStaleTmpBestEffort(filePath, now, maxAgeMs) {
|
|
32
32
|
try {
|
|
33
|
-
const stat =
|
|
33
|
+
const stat = fs.statSync(filePath);
|
|
34
34
|
if (stat.isFile() && now - stat.mtimeMs >= maxAgeMs) {
|
|
35
35
|
await unlinkBestEffort(filePath);
|
|
36
36
|
}
|
|
@@ -113,22 +113,27 @@ async function isDarwinSystemAlias(dir, stat) {
|
|
|
113
113
|
if (resolved !== "/tmp" && resolved !== "/var") {
|
|
114
114
|
return false;
|
|
115
115
|
}
|
|
116
|
-
|
|
116
|
+
try {
|
|
117
|
+
return fs.realpathSync.native(resolved) === `/private${resolved}`;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
117
122
|
}
|
|
118
123
|
async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissing) {
|
|
119
124
|
let base = path.resolve(validationRoot.path);
|
|
120
125
|
let target = path.resolve(dir);
|
|
121
126
|
let current = base;
|
|
122
|
-
let baseStat =
|
|
127
|
+
let baseStat = fs.lstatSync(base);
|
|
123
128
|
if (baseStat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
|
|
124
129
|
const relative = path.relative(base, target);
|
|
125
130
|
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
126
131
|
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
127
132
|
}
|
|
128
|
-
base =
|
|
133
|
+
base = fs.realpathSync.native(base);
|
|
129
134
|
target = path.join(base, ...relative.split(path.sep).filter(Boolean));
|
|
130
135
|
current = base;
|
|
131
|
-
baseStat =
|
|
136
|
+
baseStat = fs.lstatSync(base);
|
|
132
137
|
}
|
|
133
138
|
if (baseStat.isSymbolicLink() || !baseStat.isDirectory()) {
|
|
134
139
|
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
@@ -142,7 +147,7 @@ async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissin
|
|
|
142
147
|
current = path.join(current, segment);
|
|
143
148
|
let stat;
|
|
144
149
|
try {
|
|
145
|
-
stat =
|
|
150
|
+
stat = fs.lstatSync(current);
|
|
146
151
|
}
|
|
147
152
|
catch (error) {
|
|
148
153
|
if (allowMissing && error.code === "ENOENT") {
|
|
@@ -153,7 +158,7 @@ async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissin
|
|
|
153
158
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
154
159
|
if (stat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
|
|
155
160
|
if (await isDarwinSystemAlias(current, stat)) {
|
|
156
|
-
current =
|
|
161
|
+
current = fs.realpathSync.native(current);
|
|
157
162
|
continue;
|
|
158
163
|
}
|
|
159
164
|
}
|
|
@@ -172,7 +177,7 @@ async function chmodQueueDirectory(dir) {
|
|
|
172
177
|
let handle;
|
|
173
178
|
try {
|
|
174
179
|
handle = await fs.promises.open(dir, fs.constants.O_RDONLY | noFollow | directoryFlag);
|
|
175
|
-
const stat =
|
|
180
|
+
const stat = fs.fstatSync(handle.fd);
|
|
176
181
|
if (!stat.isDirectory()) {
|
|
177
182
|
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
178
183
|
}
|
|
@@ -193,7 +198,7 @@ async function chmodQueueDirectory(dir) {
|
|
|
193
198
|
}
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
|
-
const stat =
|
|
201
|
+
const stat = fs.lstatSync(dir);
|
|
197
202
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
198
203
|
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
199
204
|
}
|
|
@@ -218,7 +223,7 @@ async function inspectQueueEntry(inspect, maxBytes, expected) {
|
|
|
218
223
|
try {
|
|
219
224
|
return await inspectFileIdentity(async () => {
|
|
220
225
|
try {
|
|
221
|
-
const stat =
|
|
226
|
+
const stat = inspect();
|
|
222
227
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
223
228
|
throw new Error("queue entry is not a regular file");
|
|
224
229
|
}
|
|
@@ -242,11 +247,11 @@ async function inspectQueueEntry(inspect, maxBytes, expected) {
|
|
|
242
247
|
}
|
|
243
248
|
}
|
|
244
249
|
async function readBoundedUtf8File(params) {
|
|
245
|
-
const inspectPath = () => fs.
|
|
250
|
+
const inspectPath = () => fs.lstatSync(params.filePath, { bigint: true });
|
|
246
251
|
const initialStat = await inspectQueueEntry(inspectPath, params.maxBytes);
|
|
247
252
|
const handle = await fs.promises.open(params.filePath, resolveReadOpenFlags());
|
|
248
253
|
try {
|
|
249
|
-
const openedStat = await inspectQueueEntry(() => handle.
|
|
254
|
+
const openedStat = await inspectQueueEntry(() => fs.fstatSync(handle.fd, { bigint: true }), params.maxBytes, initialStat);
|
|
250
255
|
await inspectQueueEntry(inspectPath, params.maxBytes, openedStat);
|
|
251
256
|
const chunks = [];
|
|
252
257
|
const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, params.maxBytes + 1));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-path-cleanup.d.ts","sourceRoot":"","sources":["../src/move-path-cleanup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"move-path-cleanup.d.ts","sourceRoot":"","sources":["../src/move-path-cleanup.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,CAAC,aAAa,GAAG;IACf,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAAC;IACjE,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC,GACF,CAAC,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEvC,KAAK,wBAAwB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpD,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAC7C,CAAC;AAEF,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,aAAa,CAUhB;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAUxE;AAMD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,CAI5D;AAED,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAIf;AA0BD,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,mBAAmB,GAC5B,uBAAuB,CAuBzB;AAsDD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,uBAAuB,EAC9B,oBAAoB,EAAE,MAAM,IAAI,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CAyDnC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
export function entryIdentity(stat) {
|
|
@@ -29,7 +30,7 @@ export function sourceChangedError(sourcePath) {
|
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
export async function assertSourceStillMatches(sourcePath, identity) {
|
|
32
|
-
if (!sameIdentity(identity, entryIdentity(
|
|
33
|
+
if (!sameIdentity(identity, entryIdentity(fsSync.lstatSync(sourcePath)))) {
|
|
33
34
|
throw sourceChangedError(sourcePath);
|
|
34
35
|
}
|
|
35
36
|
}
|
|
@@ -93,7 +94,7 @@ async function observeOwnedAliasUnlink(sourcePath, group) {
|
|
|
93
94
|
}
|
|
94
95
|
let observed;
|
|
95
96
|
try {
|
|
96
|
-
observed =
|
|
97
|
+
observed = fsSync.lstatSync(remainingPath);
|
|
97
98
|
}
|
|
98
99
|
catch (error) {
|
|
99
100
|
if (error?.code === "ENOENT") {
|
|
@@ -118,7 +119,7 @@ export async function cleanupCopiedEntry(sourcePath, manifest, state, assertBefo
|
|
|
118
119
|
}
|
|
119
120
|
let currentStat;
|
|
120
121
|
try {
|
|
121
|
-
currentStat =
|
|
122
|
+
currentStat = fsSync.lstatSync(sourcePath);
|
|
122
123
|
}
|
|
123
124
|
catch (error) {
|
|
124
125
|
if (error?.code === "ENOENT") {
|
package/dist/move-path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,KAAK,sBAAsB,GAAG,cAAc,GAAG,uBAAuB,CAAC;AAEvE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,GAAG,SAAS,CASpC;
|
|
1
|
+
{"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,KAAK,sBAAsB,GAAG,cAAc,GAAG,uBAAuB,CAAC;AAEvE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,GAAG,SAAS,CASpC;AAuQD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAqHf"}
|
package/dist/move-path.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { constants as fsConstants } from "node:fs";
|
|
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
5
|
import { FsSafeError } from "./errors.js";
|
|
@@ -29,7 +29,7 @@ async function preflightSourceHardlinks(sourcePath) {
|
|
|
29
29
|
let discovered = 1;
|
|
30
30
|
while (pending.length > 0) {
|
|
31
31
|
const current = pending.pop();
|
|
32
|
-
const stat =
|
|
32
|
+
const stat = fsSync.lstatSync(current);
|
|
33
33
|
if (stat.isFile() && stat.nlink > 1) {
|
|
34
34
|
throw hardlinkedSourceError(current);
|
|
35
35
|
}
|
|
@@ -52,21 +52,19 @@ function isSameOrDescendant(parentPath, candidatePath) {
|
|
|
52
52
|
(!path.isAbsolute(relative) && !relative.startsWith(`..${path.sep}`) && relative !== ".."));
|
|
53
53
|
}
|
|
54
54
|
async function assertCopyDestinationOutsideSource(sourcePath, targetPath, expectedIdentity) {
|
|
55
|
-
const sourceStat =
|
|
55
|
+
const sourceStat = fsSync.lstatSync(sourcePath);
|
|
56
56
|
const sourceIdentity = entryIdentity(sourceStat);
|
|
57
57
|
if (expectedIdentity && !sameIdentity(expectedIdentity, sourceIdentity)) {
|
|
58
58
|
throw sourceChangedError(sourcePath);
|
|
59
59
|
}
|
|
60
60
|
const normalizedSource = path.resolve(sourcePath);
|
|
61
61
|
const normalizedTarget = path.resolve(targetPath);
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
fs.realpath(path.dirname(normalizedTarget)),
|
|
65
|
-
]);
|
|
62
|
+
const sourceParentReal = fsSync.realpathSync.native(path.dirname(normalizedSource));
|
|
63
|
+
const targetParentReal = fsSync.realpathSync.native(path.dirname(normalizedTarget));
|
|
66
64
|
const sourceCandidate = path.join(sourceParentReal, path.basename(normalizedSource));
|
|
67
65
|
const targetCandidate = path.join(targetParentReal, path.basename(normalizedTarget));
|
|
68
66
|
const sourceBoundary = sourceStat.isDirectory()
|
|
69
|
-
?
|
|
67
|
+
? fsSync.realpathSync.native(sourcePath)
|
|
70
68
|
: sourceCandidate;
|
|
71
69
|
const unsafeTarget = sourceStat.isDirectory()
|
|
72
70
|
? isSameOrDescendant(sourceBoundary, targetCandidate)
|
|
@@ -103,6 +101,7 @@ async function writeAll(handle, buffer, bytesRead) {
|
|
|
103
101
|
}
|
|
104
102
|
async function copyRegularFilePinned(params) {
|
|
105
103
|
let destinationCreated = false;
|
|
104
|
+
let openedIdentity;
|
|
106
105
|
let sourceHandle;
|
|
107
106
|
try {
|
|
108
107
|
sourceHandle = await fs.open(params.from, resolveReadOpenFlags());
|
|
@@ -115,13 +114,24 @@ async function copyRegularFilePinned(params) {
|
|
|
115
114
|
throw error;
|
|
116
115
|
}
|
|
117
116
|
try {
|
|
118
|
-
const openedStat =
|
|
117
|
+
const openedStat = fsSync.fstatSync(sourceHandle.fd);
|
|
118
|
+
openedIdentity = entryIdentity(openedStat);
|
|
119
119
|
if (params.rejectHardlinks && openedStat.nlink > 1) {
|
|
120
120
|
throw hardlinkedSourceError(params.from);
|
|
121
121
|
}
|
|
122
|
-
|
|
122
|
+
const openAdvancedWindowsCtime = process.platform === "win32" &&
|
|
123
|
+
params.identity.dev === openedIdentity.dev &&
|
|
124
|
+
params.identity.ino === openedIdentity.ino &&
|
|
125
|
+
params.identity.mode === openedIdentity.mode &&
|
|
126
|
+
params.identity.nlink === openedIdentity.nlink &&
|
|
127
|
+
params.identity.size === openedIdentity.size &&
|
|
128
|
+
params.identity.mtimeMs === openedIdentity.mtimeMs &&
|
|
129
|
+
openedIdentity.ctimeMs >= params.identity.ctimeMs;
|
|
130
|
+
if (!openedStat.isFile() ||
|
|
131
|
+
(!sameIdentity(params.identity, openedIdentity) && !openAdvancedWindowsCtime)) {
|
|
123
132
|
throw sourceChangedError(params.from);
|
|
124
133
|
}
|
|
134
|
+
await assertSourceStillMatches(params.from, openedIdentity);
|
|
125
135
|
const destinationHandle = await fs.open(params.to, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, modeBits(params.mode) || 0o666);
|
|
126
136
|
destinationCreated = true;
|
|
127
137
|
try {
|
|
@@ -135,11 +145,11 @@ async function copyRegularFilePinned(params) {
|
|
|
135
145
|
}
|
|
136
146
|
// Re-check the opened source before the staged tree can be committed. If
|
|
137
147
|
// it changed while we copied, the caller should retry the move.
|
|
138
|
-
const finalSourceStat =
|
|
148
|
+
const finalSourceStat = fsSync.fstatSync(sourceHandle.fd);
|
|
139
149
|
if (params.rejectHardlinks && finalSourceStat.nlink > 1) {
|
|
140
150
|
throw hardlinkedSourceError(params.from);
|
|
141
151
|
}
|
|
142
|
-
if (!sameIdentity(
|
|
152
|
+
if (!sameIdentity(openedIdentity, entryIdentity(finalSourceStat))) {
|
|
143
153
|
throw sourceChangedError(params.from);
|
|
144
154
|
}
|
|
145
155
|
await destinationHandle.chmod(modeBits(params.mode));
|
|
@@ -157,15 +167,18 @@ async function copyRegularFilePinned(params) {
|
|
|
157
167
|
finally {
|
|
158
168
|
await sourceHandle.close();
|
|
159
169
|
}
|
|
170
|
+
return openedIdentity;
|
|
160
171
|
}
|
|
161
172
|
async function copyEntryWithManifest(from, to, options, expectedIdentity) {
|
|
162
|
-
const sourceStat =
|
|
173
|
+
const sourceStat = fsSync.lstatSync(from);
|
|
163
174
|
const identity = entryIdentity(sourceStat);
|
|
164
175
|
if (expectedIdentity && !sameIdentity(expectedIdentity, identity)) {
|
|
165
176
|
throw sourceChangedError(from);
|
|
166
177
|
}
|
|
167
178
|
if (sourceStat.isSymbolicLink()) {
|
|
168
|
-
|
|
179
|
+
const target = await fs.readlink(from);
|
|
180
|
+
const targetType = process.platform === "win32" && fsSync.statSync(from).isDirectory() ? "junction" : undefined;
|
|
181
|
+
await fs.symlink(target, to, targetType);
|
|
169
182
|
// readlink() is path-based; verify the symlink we copied is still the one
|
|
170
183
|
// we inspected before letting the staged destination become visible.
|
|
171
184
|
await assertSourceStillMatches(from, identity);
|
|
@@ -202,14 +215,14 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
|
|
|
202
215
|
if (options.sourceHardlinks === "reject" && sourceStat.nlink > 1) {
|
|
203
216
|
throw hardlinkedSourceError(from);
|
|
204
217
|
}
|
|
205
|
-
await copyRegularFilePinned({
|
|
218
|
+
const copiedIdentity = await copyRegularFilePinned({
|
|
206
219
|
from,
|
|
207
220
|
identity,
|
|
208
221
|
mode: sourceStat.mode,
|
|
209
222
|
rejectHardlinks: options.sourceHardlinks === "reject",
|
|
210
223
|
to,
|
|
211
224
|
});
|
|
212
|
-
return { ...
|
|
225
|
+
return { ...copiedIdentity, kind: "leaf" };
|
|
213
226
|
}
|
|
214
227
|
function assertSynchronousResult(returned, name) {
|
|
215
228
|
if (returned !== undefined) {
|
|
@@ -293,7 +306,7 @@ export async function movePathWithCopyFallback(options) {
|
|
|
293
306
|
...(rejectHardlinks ? { budget: { discovered: 1 } } : {}),
|
|
294
307
|
}, sourceIdentity);
|
|
295
308
|
const cleanupState = createCleanupCopiedEntryState(sourcePath, manifest);
|
|
296
|
-
unregisterStaged.setIdentity(
|
|
309
|
+
unregisterStaged.setIdentity(fsSync.lstatSync(staged, { bigint: true }));
|
|
297
310
|
await assertCopyDestinationOutsideSource(sourcePath, targetPath, manifest);
|
|
298
311
|
await guardedRename({
|
|
299
312
|
from: staged,
|
|
@@ -313,7 +326,7 @@ export async function movePathWithCopyFallback(options) {
|
|
|
313
326
|
finally {
|
|
314
327
|
if (!destinationPublished) {
|
|
315
328
|
try {
|
|
316
|
-
const stagedIdentity =
|
|
329
|
+
const stagedIdentity = fsSync.lstatSync(staged, { bigint: true });
|
|
317
330
|
if (!stagedIdentity.isSymbolicLink())
|
|
318
331
|
unregisterStaged.setIdentity(stagedIdentity);
|
|
319
332
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
2
|
import type { FileHandle } from "node:fs/promises";
|
|
3
3
|
export declare function recordFileOpenFailure(error: unknown, filePath: string): never;
|
|
4
4
|
export declare function recordExclusiveCreateFailure(error: unknown, filePath: string): never;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opened-file-failure.d.ts","sourceRoot":"","sources":["../src/opened-file-failure.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"opened-file-failure.d.ts","sourceRoot":"","sources":["../src/opened-file-failure.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,CAG7E;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,CAGpF;AAED,wBAAgB,yBAAyB,CACvC,KAAK,GAAE,KAA8E,GACpF,KAAK,CAGP;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC,CASf"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
1
2
|
import { FsSafeError } from "./errors.js";
|
|
2
3
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
3
4
|
import { isFileObservationFailure, recordFileObservationFailure } from "./file-observation.js";
|
|
@@ -19,7 +20,7 @@ export async function recordPreOpenFileChange(error, handle, filePath, before, o
|
|
|
19
20
|
return;
|
|
20
21
|
try {
|
|
21
22
|
await inspectFileIdentity(async () => before);
|
|
22
|
-
const current = await inspectFileIdentity(() => handle.
|
|
23
|
+
const current = await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }), opened);
|
|
23
24
|
if (current.isFile() && [0n, 1n].includes(current.nlink) &&
|
|
24
25
|
(before.dev !== current.dev || before.ino !== current.ino)) {
|
|
25
26
|
// A stale pathname sample is not proof that its former inode was unlinked.
|
|
@@ -35,7 +36,7 @@ export async function recordOpenedFileFailure(error, handle, filePath, identity)
|
|
|
35
36
|
!identity.isFile() || identity.nlink > 1n)
|
|
36
37
|
return;
|
|
37
38
|
try {
|
|
38
|
-
const current = await inspectFileIdentity(() => handle.
|
|
39
|
+
const current = await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
|
|
39
40
|
if (current.isFile() && current.nlink === 0n)
|
|
40
41
|
recordFileObservationFailure(error, `unlinked:${filePath}`);
|
|
41
42
|
}
|
package/dist/permissions.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
2
|
import { formatIcaclsResetCommand, inspectWindowsPermissions, } from "./permissions-windows.js";
|
|
3
3
|
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
|
|
4
4
|
export async function safeStat(targetPath) {
|
|
5
5
|
try {
|
|
6
|
-
const lst =
|
|
6
|
+
const lst = fsSync.lstatSync(targetPath);
|
|
7
7
|
return {
|
|
8
8
|
ok: true,
|
|
9
9
|
isSymlink: lst.isSymbolicLink(),
|
|
@@ -46,7 +46,7 @@ export async function inspectPathPermissions(targetPath, opts) {
|
|
|
46
46
|
let effectiveIsDir = st.isDir;
|
|
47
47
|
if (st.isSymlink) {
|
|
48
48
|
try {
|
|
49
|
-
const target =
|
|
49
|
+
const target = fsSync.statSync(targetPath);
|
|
50
50
|
effectiveMode = typeof target.mode === "number" ? target.mode : st.mode;
|
|
51
51
|
effectiveIsDir = target.isDirectory();
|
|
52
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAChC,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EACV,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,0BAA0B,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GACtC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC/C,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAC3D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAClD,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,CAAC;IAC5F,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,IAAI,0BAA0B,CAAC;IACtC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;
|
|
1
|
+
{"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,7 +67,7 @@ function throwTempWorkspaceOpenFailure(failure) {
|
|
|
67
67
|
}
|
|
68
68
|
async function ensurePrivateDirectory(dir, mode) {
|
|
69
69
|
await fs.mkdir(dir, { recursive: true, mode });
|
|
70
|
-
const stat =
|
|
70
|
+
const stat = fsSync.statSync(dir);
|
|
71
71
|
if (!stat.isDirectory()) {
|
|
72
72
|
throw new Error(`Temp root must be a directory: ${dir}`);
|
|
73
73
|
}
|
|
@@ -91,7 +91,13 @@ async function createTempWorkspace(options) {
|
|
|
91
91
|
const mode = options.mode ?? 0o600;
|
|
92
92
|
const cleanupSafety = resolveTempWorkspaceCleanupSafety(options.cleanupSafety);
|
|
93
93
|
const requestedRoot = path.resolve(options.rootDir);
|
|
94
|
-
|
|
94
|
+
let root = requestedRoot;
|
|
95
|
+
try {
|
|
96
|
+
root = fsSync.realpathSync.native(requestedRoot);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
root = requestedRoot;
|
|
100
|
+
}
|
|
95
101
|
await ensurePrivateDirectory(root, dirMode);
|
|
96
102
|
const capability = new TempWorkspaceCleanupCapability(root, cleanupSafety);
|
|
97
103
|
let dir;
|
|
@@ -101,7 +107,7 @@ async function createTempWorkspace(options) {
|
|
|
101
107
|
try {
|
|
102
108
|
dir = await fs.mkdtemp(path.join(root, sanitizeTempPrefix(options.prefix)));
|
|
103
109
|
await fs.chmod(dir, dirMode).catch(() => undefined);
|
|
104
|
-
stat =
|
|
110
|
+
stat = fsSync.lstatSync(dir, { bigint: true });
|
|
105
111
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
106
112
|
throw new Error(`Temp workspace must be a directory: ${dir}`);
|
|
107
113
|
}
|
package/dist/publish-file.js
CHANGED
|
@@ -40,8 +40,8 @@ async function openNativeParent(filePath) {
|
|
|
40
40
|
const parentPath = path.dirname(filePath);
|
|
41
41
|
const handle = await fs.open(parentPath, directoryOpenFlags());
|
|
42
42
|
try {
|
|
43
|
-
const pathname =
|
|
44
|
-
const opened =
|
|
43
|
+
const pathname = fsSync.lstatSync(parentPath, { bigint: true });
|
|
44
|
+
const opened = fsSync.fstatSync(handle.fd, { bigint: true });
|
|
45
45
|
if (pathname.isSymbolicLink() || !sameFileIdentity(pathname, opened)) {
|
|
46
46
|
throw new FsSafeError("path-mismatch", "publication parent changed while opening");
|
|
47
47
|
}
|
|
@@ -55,8 +55,8 @@ async function openNativeParent(filePath) {
|
|
|
55
55
|
async function assertPinnedSourceCurrent(params) {
|
|
56
56
|
// Windows file indexes can exceed Number.MAX_SAFE_INTEGER, so publication
|
|
57
57
|
// fences must compare bigint stats instead of rounded numeric identities.
|
|
58
|
-
const opened =
|
|
59
|
-
const current =
|
|
58
|
+
const opened = fsSync.fstatSync(params.handle.fd, { bigint: true });
|
|
59
|
+
const current = fsSync.lstatSync(params.sourcePath, { bigint: true });
|
|
60
60
|
if (!opened.isFile() ||
|
|
61
61
|
current.isSymbolicLink() ||
|
|
62
62
|
!current.isFile() ||
|
|
@@ -96,9 +96,9 @@ async function copyPinnedSource(params) {
|
|
|
96
96
|
fsSync.fchmodSync(nativeFd, 0o600);
|
|
97
97
|
createdIdentity = fsSync.fstatSync(nativeFd, { bigint: true });
|
|
98
98
|
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("exclusive-copy", params.targetPath, createdIdentity);
|
|
99
|
-
const identity =
|
|
99
|
+
const identity = fsSync.lstatSync(params.targetPath, { bigint: true });
|
|
100
100
|
target = await fs.open(params.targetPath, sourceOpenFlags());
|
|
101
|
-
const opened =
|
|
101
|
+
const opened = fsSync.fstatSync(target.fd, { bigint: true });
|
|
102
102
|
if (identity.isSymbolicLink() ||
|
|
103
103
|
!identity.isFile() ||
|
|
104
104
|
!sameFileIdentity(createdIdentity, identity) ||
|
|
@@ -126,11 +126,11 @@ async function copyPinnedSource(params) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
const target = await fs.open(params.targetPath, "wx+", 0o600);
|
|
129
|
-
const createdIdentity =
|
|
129
|
+
const createdIdentity = fsSync.fstatSync(target.fd, { bigint: true });
|
|
130
130
|
rememberCreatedTarget(params.failure, createdIdentity, "copy-verify");
|
|
131
131
|
try {
|
|
132
132
|
await target.chmod(0o600);
|
|
133
|
-
const exactIdentity =
|
|
133
|
+
const exactIdentity = fsSync.fstatSync(target.fd, { bigint: true });
|
|
134
134
|
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("exclusive-copy", params.targetPath, exactIdentity);
|
|
135
135
|
const hash = createHash("sha256");
|
|
136
136
|
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
@@ -164,7 +164,7 @@ async function removeCreatedTargetIfUnchanged(targetPath, identity) {
|
|
|
164
164
|
return "unknown";
|
|
165
165
|
}
|
|
166
166
|
try {
|
|
167
|
-
const current =
|
|
167
|
+
const current = fsSync.lstatSync(targetPath, { bigint: true });
|
|
168
168
|
if (!current.isSymbolicLink() && sameFileIdentityForCleanup(current, identity)) {
|
|
169
169
|
await fs.rm(targetPath);
|
|
170
170
|
return "removed";
|
|
@@ -195,7 +195,7 @@ export async function publishFileExclusive(params) {
|
|
|
195
195
|
if (params.parentReceipt && path.resolve(params.parentReceipt.path) !== parentPath) {
|
|
196
196
|
throw new FsSafeError("path-mismatch", "publication parent receipt does not match target parent");
|
|
197
197
|
}
|
|
198
|
-
const sourcePathStat =
|
|
198
|
+
const sourcePathStat = fsSync.lstatSync(sourcePath);
|
|
199
199
|
if (sourcePathStat.isSymbolicLink() || !sourcePathStat.isFile()) {
|
|
200
200
|
throw new FsSafeError("not-file", "publication source must be a regular file");
|
|
201
201
|
}
|
|
@@ -212,9 +212,9 @@ export async function publishFileExclusive(params) {
|
|
|
212
212
|
parent = await pinDirectory(params.parentReceipt ?? parentPath, {
|
|
213
213
|
label: "publication parent",
|
|
214
214
|
});
|
|
215
|
-
const sourceIdentity =
|
|
216
|
-
const sourceExactIdentity =
|
|
217
|
-
const sourcePathExactIdentity =
|
|
215
|
+
const sourceIdentity = fsSync.fstatSync(source.fd);
|
|
216
|
+
const sourceExactIdentity = fsSync.fstatSync(source.fd, { bigint: true });
|
|
217
|
+
const sourcePathExactIdentity = fsSync.lstatSync(sourcePath, { bigint: true });
|
|
218
218
|
if (sourcePathExactIdentity.isSymbolicLink() ||
|
|
219
219
|
!sourcePathExactIdentity.isFile() ||
|
|
220
220
|
!sameFileIdentity(sourcePathExactIdentity, sourceExactIdentity) ||
|
|
@@ -241,14 +241,14 @@ export async function publishFileExclusive(params) {
|
|
|
241
241
|
// A failed post-rename fence must not delete the only remaining name.
|
|
242
242
|
failure.preserveTarget = true;
|
|
243
243
|
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("rename-noreplace", targetPath, sourceExactIdentity);
|
|
244
|
-
const targetExactIdentity =
|
|
244
|
+
const targetExactIdentity = fsSync.lstatSync(targetPath, { bigint: true });
|
|
245
245
|
if (targetExactIdentity.isSymbolicLink() ||
|
|
246
246
|
!targetExactIdentity.isFile() ||
|
|
247
247
|
!sameFileIdentity(targetExactIdentity, sourceExactIdentity)) {
|
|
248
248
|
throw new FsSafeError("path-mismatch", "no-replace publication target changed");
|
|
249
249
|
}
|
|
250
250
|
try {
|
|
251
|
-
|
|
251
|
+
fsSync.lstatSync(sourcePath);
|
|
252
252
|
throw new FsSafeError("path-mismatch", "no-replace publication source still exists");
|
|
253
253
|
}
|
|
254
254
|
catch (error) {
|
|
@@ -257,7 +257,7 @@ export async function publishFileExclusive(params) {
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
syncFileBestEffortSync(sourceNativeParent.handle.fd);
|
|
260
|
-
const targetIdentity =
|
|
260
|
+
const targetIdentity = fsSync.lstatSync(targetPath);
|
|
261
261
|
return {
|
|
262
262
|
method: "rename-noreplace",
|
|
263
263
|
identity: targetIdentity,
|
|
@@ -278,14 +278,14 @@ export async function publishFileExclusive(params) {
|
|
|
278
278
|
}
|
|
279
279
|
rememberCreatedTarget(failure, sourceExactIdentity, "hardlink-verify");
|
|
280
280
|
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("hardlink", targetPath, sourceExactIdentity);
|
|
281
|
-
const targetExactIdentity =
|
|
281
|
+
const targetExactIdentity = fsSync.lstatSync(targetPath, { bigint: true });
|
|
282
282
|
if (targetExactIdentity.isSymbolicLink() ||
|
|
283
283
|
!targetExactIdentity.isFile() ||
|
|
284
284
|
!sameFileIdentity(targetExactIdentity, sourceExactIdentity)) {
|
|
285
285
|
throw new FsSafeError("path-mismatch", "hardlink publication target changed");
|
|
286
286
|
}
|
|
287
287
|
await assertPinnedSourceCurrent({ sourcePath, handle: source, identity: sourceExactIdentity });
|
|
288
|
-
const targetIdentity =
|
|
288
|
+
const targetIdentity = fsSync.lstatSync(targetPath);
|
|
289
289
|
return {
|
|
290
290
|
method: "hardlink",
|
|
291
291
|
identity: targetIdentity,
|
|
@@ -316,9 +316,9 @@ export async function publishFileExclusive(params) {
|
|
|
316
316
|
failure,
|
|
317
317
|
});
|
|
318
318
|
target = copied.handle;
|
|
319
|
-
targetIdentity =
|
|
320
|
-
const targetPathStat =
|
|
321
|
-
const targetPathExactStat =
|
|
319
|
+
targetIdentity = fsSync.fstatSync(target.fd);
|
|
320
|
+
const targetPathStat = fsSync.lstatSync(targetPath);
|
|
321
|
+
const targetPathExactStat = fsSync.lstatSync(targetPath, { bigint: true });
|
|
322
322
|
const copiedBack = await hashFileHandle(target, native);
|
|
323
323
|
const sourceAfter = await hashFileHandle(source, native);
|
|
324
324
|
if (targetPathStat.isSymbolicLink() ||
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AA0FF,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,IAAI,CAwBN;AAoID,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,CA4DhB;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"}
|