@openclaw/fs-safe 0.5.0 → 0.5.2
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 +79 -0
- package/README.md +18 -6
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +20 -12
- package/dist/archive-entry.d.ts +1 -0
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +25 -5
- package/dist/archive-input.d.ts +7 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +11 -1
- package/dist/archive-limits.js +1 -1
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +6 -3
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +2 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +30 -12
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +3 -2
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +7 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +7 -4
- package/dist/archive-zip-entry.d.ts +1 -0
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-integrity.d.ts +4 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -0
- package/dist/archive-zip-integrity.js +44 -0
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +6 -1
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +5 -2
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +13 -2
- package/dist/deny-mutations.d.ts +1 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +6 -39
- package/dist/device-path.d.ts +1 -0
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +30 -8
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +5 -4
- package/dist/error-detail.d.ts +2 -0
- package/dist/error-detail.d.ts.map +1 -0
- package/dist/error-detail.js +4 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +4 -1
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +88 -35
- package/dist/file-store-boundary.d.ts +6 -0
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -5
- package/dist/file-store-sync-write.d.ts +9 -0
- package/dist/file-store-sync-write.d.ts.map +1 -0
- package/dist/file-store-sync-write.js +97 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +36 -128
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +27 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +5 -6
- package/dist/install-path.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +19 -13
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +1 -3
- package/dist/local-file-access.d.ts +3 -2
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +12 -6
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -30
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +51 -12
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
- package/dist/native-binding.d.ts +1 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +14 -1
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +44 -17
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +6 -5
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +1 -7
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +7 -2
- package/dist/permission-exec.d.ts +6 -0
- package/dist/permission-exec.d.ts.map +1 -0
- package/dist/permission-exec.js +28 -0
- package/dist/permissions-windows.d.ts +56 -0
- package/dist/permissions-windows.d.ts.map +1 -0
- package/dist/permissions-windows.js +285 -0
- package/dist/permissions.d.ts +3 -46
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +17 -328
- package/dist/pinned-write.js +2 -1
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +21 -2
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +10 -1
- package/dist/replace-file-copy-fallback.d.ts +3 -0
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +18 -11
- package/dist/replace-file-descriptor.d.ts +33 -0
- package/dist/replace-file-descriptor.d.ts.map +1 -0
- package/dist/replace-file-descriptor.js +96 -0
- package/dist/replace-file.d.ts +9 -2
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +64 -72
- package/dist/root-context.d.ts +8 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +43 -2
- package/dist/root-errors.d.ts +7 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +46 -1
- package/dist/root-file.d.ts +1 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +2 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +213 -168
- package/dist/root-path-symlink.d.ts +3 -0
- package/dist/root-path-symlink.d.ts.map +1 -0
- package/dist/root-path-symlink.js +35 -0
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +129 -190
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +15 -0
- package/dist/safe-path-segment.d.ts +2 -0
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +28 -4
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +19 -3
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +6 -1
- package/dist/short-path.d.ts +2 -0
- package/dist/short-path.d.ts.map +1 -0
- package/dist/short-path.js +7 -0
- package/dist/sidecar-lock-acquire.d.ts +30 -0
- package/dist/sidecar-lock-acquire.d.ts.map +1 -0
- package/dist/sidecar-lock-acquire.js +260 -0
- package/dist/sidecar-lock-policy.d.ts +3 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +20 -3
- package/dist/sidecar-lock-reclaim.d.ts +5 -1
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +59 -9
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +27 -226
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +66 -7
- package/dist/test-hooks.d.ts +1 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/timing.d.ts +2 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +9 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +15 -0
- package/docs/advanced.md +1 -1
- package/docs/archive.md +27 -20
- package/docs/assets/readme-banner.jpg +0 -0
- package/docs/atomic.md +48 -19
- package/docs/errors.md +31 -7
- package/docs/file-store.md +10 -1
- package/docs/filename.md +17 -16
- package/docs/index.md +1 -0
- package/docs/install-path.md +9 -6
- package/docs/json.md +8 -2
- package/docs/local-roots.md +79 -103
- package/docs/native-helper.md +11 -5
- package/docs/native.md +2 -2
- package/docs/output.md +8 -3
- package/docs/path-scope.md +4 -1
- package/docs/path.md +7 -3
- package/docs/permissions.md +8 -0
- package/docs/public-api.md +109 -0
- package/docs/quickstart.md +6 -2
- package/docs/reading.md +13 -10
- package/docs/root.md +26 -4
- package/docs/secret-file.md +17 -4
- package/docs/security-model.md +21 -4
- package/docs/sidecar-lock.md +15 -5
- package/docs/temp.md +37 -5
- package/docs/test-hooks.md +2 -0
- package/docs/types.md +32 -15
- package/docs/writing.md +25 -12
- package/package.json +10 -6
- package/dist/archive-file-io.d.ts +0 -9
- package/dist/archive-file-io.d.ts.map +0 -1
- package/dist/archive-file-io.js +0 -11
- package/dist/file-store-source.d.ts +0 -5
- package/dist/file-store-source.d.ts.map +0 -1
- package/dist/file-store-source.js +0 -30
- package/dist/file-url.d.ts +0 -2
- package/dist/file-url.d.ts.map +0 -1
- package/dist/file-url.js +0 -1
- package/dist/path-stat.d.ts +0 -4
- package/dist/path-stat.d.ts.map +0 -1
- package/dist/path-stat.js +0 -15
- package/dist/windows-permissions-native.d.ts +0 -9
- package/dist/windows-permissions-native.d.ts.map +0 -1
- package/dist/windows-permissions-native.js +0 -31
package/dist/permissions.js
CHANGED
|
@@ -1,52 +1,6 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import fs from "node:fs/promises";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
7
|
-
import { inspectWindowsPermissionsNative } from "./windows-permissions-native.js";
|
|
8
|
-
import { resolveWindowsSystemCommand } from "./windows-command.js";
|
|
9
|
-
import { inspectWindowsOwner, resolveWindowsCurrentUserSid, resolveWindowsPrincipalSids, } from "./windows-owner.js";
|
|
10
|
-
const execFileAsync = promisify(execFile);
|
|
11
|
-
const INHERIT_FLAGS = new Set(["I", "OI", "CI", "IO", "NP"]);
|
|
12
|
-
const WORLD_PRINCIPALS = new Set(["everyone", "users", "builtin\\users", "authenticated users", "nt authority\\authenticated users", "anonymous logon", "nt authority\\anonymous logon", "guests", "builtin\\guests", "interactive", "nt authority\\interactive", "network", "nt authority\\network", "local"]);
|
|
13
|
-
const TRUSTED_BASE = new Set([
|
|
14
|
-
"nt authority\\system",
|
|
15
|
-
"system",
|
|
16
|
-
"builtin\\administrators",
|
|
17
|
-
"creator owner",
|
|
18
|
-
"autorite nt\\système",
|
|
19
|
-
"nt-autorität\\system",
|
|
20
|
-
"autoridad nt\\system",
|
|
21
|
-
"autoridade nt\\system",
|
|
22
|
-
]);
|
|
23
|
-
const WORLD_SUFFIXES = ["\\users", "\\authenticated users"];
|
|
24
|
-
const SID_RE = /^\*?s-\d+-\d+(-\d+)+$/i;
|
|
25
|
-
const TRUSTED_SIDS = new Set([
|
|
26
|
-
"s-1-5-18",
|
|
27
|
-
"s-1-5-32-544",
|
|
28
|
-
"s-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464",
|
|
29
|
-
]);
|
|
30
|
-
const WORLD_SIDS = new Set(["s-1-1-0", "s-1-5-11", "s-1-5-32-545", "s-1-5-7", "s-1-5-32-546", "s-1-5-4", "s-1-2-0", "s-1-5-2"]);
|
|
31
|
-
const STATUS_PREFIXES = [
|
|
32
|
-
"successfully processed",
|
|
33
|
-
"processed",
|
|
34
|
-
"failed processing",
|
|
35
|
-
"no mapping between account names",
|
|
36
|
-
];
|
|
37
|
-
function stripDiacritics(value) {
|
|
38
|
-
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
39
|
-
}
|
|
40
|
-
const TRUSTED_BASE_ASCII = new Set([...TRUSTED_BASE].map(stripDiacritics));
|
|
41
|
-
const normalize = (value) => normalizeLowercaseStringOrEmpty(value);
|
|
42
|
-
const defaultWindowsUserInfo = () => os.userInfo();
|
|
43
|
-
function defaultPermissionExec(command, args) {
|
|
44
|
-
return execFileAsync(command, args, {
|
|
45
|
-
encoding: "utf8",
|
|
46
|
-
windowsHide: true,
|
|
47
|
-
maxBuffer: 1024 * 1024,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
2
|
+
import { formatIcaclsResetCommand, inspectWindowsPermissions, } from "./permissions-windows.js";
|
|
3
|
+
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
|
|
50
4
|
export async function safeStat(targetPath) {
|
|
51
5
|
try {
|
|
52
6
|
const lst = await fs.lstat(targetPath);
|
|
@@ -103,58 +57,14 @@ export async function inspectPathPermissions(targetPath, opts) {
|
|
|
103
57
|
const bits = modeBits(effectiveMode);
|
|
104
58
|
const platform = opts?.platform ?? process.platform;
|
|
105
59
|
if (platform === "win32") {
|
|
106
|
-
|
|
107
|
-
targetPath, stat: st, effectiveIsDir, effectiveMode, bits,
|
|
108
|
-
});
|
|
109
|
-
if (native)
|
|
110
|
-
return native;
|
|
111
|
-
const owner = await inspectWindowsOwner({
|
|
60
|
+
return await inspectWindowsPermissions({
|
|
112
61
|
targetPath,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const acl = await inspectWindowsAcl(targetPath, {
|
|
117
|
-
env: opts?.env,
|
|
118
|
-
exec: opts?.exec,
|
|
119
|
-
currentUserSid: owner.currentUserSid,
|
|
120
|
-
principalSids: owner.principalSids,
|
|
121
|
-
principalTranslationFailed: owner.principalTranslationFailed,
|
|
122
|
-
});
|
|
123
|
-
const ownerFields = {
|
|
124
|
-
...(owner.sid ? { ownerSid: owner.sid } : {}),
|
|
125
|
-
...(owner.trusted !== undefined ? { ownerTrusted: owner.trusted } : {}),
|
|
126
|
-
...(owner.error ? { ownerError: owner.error } : {}),
|
|
127
|
-
};
|
|
128
|
-
if (!acl.ok) {
|
|
129
|
-
return {
|
|
130
|
-
ok: true,
|
|
131
|
-
isSymlink: st.isSymlink,
|
|
132
|
-
isDir: effectiveIsDir,
|
|
133
|
-
mode: effectiveMode,
|
|
134
|
-
bits,
|
|
135
|
-
source: "unknown",
|
|
136
|
-
worldWritable: false,
|
|
137
|
-
groupWritable: false,
|
|
138
|
-
worldReadable: false,
|
|
139
|
-
groupReadable: false,
|
|
140
|
-
...ownerFields,
|
|
141
|
-
error: acl.error,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
ok: true,
|
|
146
|
-
isSymlink: st.isSymlink,
|
|
147
|
-
isDir: effectiveIsDir,
|
|
148
|
-
mode: effectiveMode,
|
|
62
|
+
stat: st,
|
|
63
|
+
effectiveIsDir,
|
|
64
|
+
effectiveMode,
|
|
149
65
|
bits,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
groupWritable: acl.untrustedGroup.some((entry) => entry.canWrite),
|
|
153
|
-
worldReadable: acl.untrustedWorld.some((entry) => entry.canRead),
|
|
154
|
-
groupReadable: acl.untrustedGroup.some((entry) => entry.canRead),
|
|
155
|
-
...ownerFields,
|
|
156
|
-
aclSummary: formatWindowsAclSummary(acl),
|
|
157
|
-
};
|
|
66
|
+
opts,
|
|
67
|
+
});
|
|
158
68
|
}
|
|
159
69
|
return {
|
|
160
70
|
ok: true,
|
|
@@ -182,7 +92,15 @@ export function formatPermissionRemediation(params) {
|
|
|
182
92
|
env: params.env,
|
|
183
93
|
});
|
|
184
94
|
}
|
|
185
|
-
|
|
95
|
+
const optionSeparator = params.targetPath.startsWith("-") ? "-- " : "";
|
|
96
|
+
return (`chmod ${params.posixMode.toString(8).padStart(3, "0")} ${optionSeparator}` +
|
|
97
|
+
formatPosixShellArgument(params.targetPath));
|
|
98
|
+
}
|
|
99
|
+
function formatPosixShellArgument(value) {
|
|
100
|
+
if (value && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
186
104
|
}
|
|
187
105
|
export function modeBits(mode) {
|
|
188
106
|
return mode == null ? null : mode & 0o777;
|
|
@@ -202,232 +120,3 @@ export function isWorldReadable(bits) {
|
|
|
202
120
|
export function isGroupReadable(bits) {
|
|
203
121
|
return bits != null && (bits & 0o040) !== 0;
|
|
204
122
|
}
|
|
205
|
-
function normalizeSid(value) {
|
|
206
|
-
const normalized = normalize(value);
|
|
207
|
-
return normalized.startsWith("*") ? normalized.slice(1) : normalized;
|
|
208
|
-
}
|
|
209
|
-
export function resolveWindowsUserPrincipal(env, userInfo = defaultWindowsUserInfo) {
|
|
210
|
-
const username = env?.USERNAME?.trim() || userInfo().username?.trim();
|
|
211
|
-
if (!username) {
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
const domain = env?.USERDOMAIN?.trim();
|
|
215
|
-
return domain ? `${domain}\\${username}` : username;
|
|
216
|
-
}
|
|
217
|
-
function buildTrustedPrincipals(env) {
|
|
218
|
-
const trusted = new Set(TRUSTED_BASE);
|
|
219
|
-
const principal = resolveWindowsUserPrincipal(env);
|
|
220
|
-
if (principal) {
|
|
221
|
-
trusted.add(normalize(principal));
|
|
222
|
-
const userOnly = principal.split("\\").at(-1);
|
|
223
|
-
if (userOnly) {
|
|
224
|
-
trusted.add(normalize(userOnly));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
const userSid = normalizeSid(env?.USERSID ?? "");
|
|
228
|
-
if (userSid && SID_RE.test(userSid) && !WORLD_SIDS.has(userSid)) {
|
|
229
|
-
trusted.add(userSid);
|
|
230
|
-
}
|
|
231
|
-
return trusted;
|
|
232
|
-
}
|
|
233
|
-
function classifyPrincipal(principal, trustedPrincipals) {
|
|
234
|
-
const normalized = normalize(principal);
|
|
235
|
-
if (SID_RE.test(normalized)) {
|
|
236
|
-
const sid = normalizeSid(normalized);
|
|
237
|
-
if (WORLD_SIDS.has(sid))
|
|
238
|
-
return "world";
|
|
239
|
-
if (TRUSTED_SIDS.has(sid) || trustedPrincipals.has(sid))
|
|
240
|
-
return "trusted";
|
|
241
|
-
return "group";
|
|
242
|
-
}
|
|
243
|
-
if (trustedPrincipals.has(normalized) || TRUSTED_BASE.has(normalized)) {
|
|
244
|
-
return "trusted";
|
|
245
|
-
}
|
|
246
|
-
if (WORLD_PRINCIPALS.has(normalized) ||
|
|
247
|
-
WORLD_SUFFIXES.some((suffix) => normalized.endsWith(suffix))) {
|
|
248
|
-
return "world";
|
|
249
|
-
}
|
|
250
|
-
const stripped = stripDiacritics(normalized);
|
|
251
|
-
if (stripped !== normalized && TRUSTED_BASE_ASCII.has(stripped)) {
|
|
252
|
-
return "trusted";
|
|
253
|
-
}
|
|
254
|
-
return "group";
|
|
255
|
-
}
|
|
256
|
-
function rightsFromTokens(tokens) {
|
|
257
|
-
const upper = tokens.join("").toUpperCase();
|
|
258
|
-
return {
|
|
259
|
-
canWrite: upper.includes("F") || upper.includes("M") || upper.includes("W") || upper.includes("D"),
|
|
260
|
-
canRead: upper.includes("F") || upper.includes("M") || upper.includes("R"),
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
function stripTargetPrefix(params) {
|
|
264
|
-
if (params.lowerLine.startsWith(params.lowerTarget)) {
|
|
265
|
-
return params.trimmedLine.slice(params.normalizedTarget.length).trim();
|
|
266
|
-
}
|
|
267
|
-
if (params.lowerLine.startsWith(params.quotedLower)) {
|
|
268
|
-
return params.trimmedLine.slice(params.quotedTarget.length).trim();
|
|
269
|
-
}
|
|
270
|
-
return params.trimmedLine;
|
|
271
|
-
}
|
|
272
|
-
function parseAceEntry(entry) {
|
|
273
|
-
if (!entry.includes("("))
|
|
274
|
-
return null;
|
|
275
|
-
const idx = entry.indexOf(":");
|
|
276
|
-
if (idx === -1)
|
|
277
|
-
return null;
|
|
278
|
-
const principal = entry.slice(0, idx).trim();
|
|
279
|
-
const rawRights = entry.slice(idx + 1).trim();
|
|
280
|
-
const tokens = rawRights
|
|
281
|
-
.match(/\(([^)]+)\)/g)
|
|
282
|
-
?.map((token) => token.slice(1, -1).trim())
|
|
283
|
-
.filter(Boolean) ?? [];
|
|
284
|
-
if (tokens.some((token) => token.toUpperCase() === "DENY"))
|
|
285
|
-
return null;
|
|
286
|
-
const rights = tokens.filter((token) => !INHERIT_FLAGS.has(token.toUpperCase()));
|
|
287
|
-
if (rights.length === 0)
|
|
288
|
-
return null;
|
|
289
|
-
const normalizedPrincipal = normalizeSid(principal);
|
|
290
|
-
return {
|
|
291
|
-
principal,
|
|
292
|
-
...(SID_RE.test(normalizedPrincipal) ? { sid: normalizedPrincipal } : {}),
|
|
293
|
-
rights,
|
|
294
|
-
rawRights,
|
|
295
|
-
...rightsFromTokens(rights),
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
export function parseIcaclsOutput(output, targetPath) {
|
|
299
|
-
const entries = [];
|
|
300
|
-
const normalizedTarget = targetPath.trim();
|
|
301
|
-
const lowerTarget = normalizedTarget.toLowerCase();
|
|
302
|
-
const quotedTarget = `"${normalizedTarget}"`;
|
|
303
|
-
const quotedLower = quotedTarget.toLowerCase();
|
|
304
|
-
for (const rawLine of output.split(/\r?\n/)) {
|
|
305
|
-
const line = rawLine.trimEnd();
|
|
306
|
-
if (!line.trim())
|
|
307
|
-
continue;
|
|
308
|
-
const trimmed = line.trim();
|
|
309
|
-
const lower = trimmed.toLowerCase();
|
|
310
|
-
if (STATUS_PREFIXES.some((prefix) => lower.startsWith(prefix)))
|
|
311
|
-
continue;
|
|
312
|
-
const parsed = parseAceEntry(stripTargetPrefix({
|
|
313
|
-
trimmedLine: trimmed,
|
|
314
|
-
lowerLine: lower,
|
|
315
|
-
normalizedTarget,
|
|
316
|
-
lowerTarget,
|
|
317
|
-
quotedTarget,
|
|
318
|
-
quotedLower,
|
|
319
|
-
}));
|
|
320
|
-
if (parsed)
|
|
321
|
-
entries.push(parsed);
|
|
322
|
-
}
|
|
323
|
-
return entries;
|
|
324
|
-
}
|
|
325
|
-
export function summarizeWindowsAcl(entries, env) {
|
|
326
|
-
const trustedPrincipals = buildTrustedPrincipals(env);
|
|
327
|
-
const trusted = [];
|
|
328
|
-
const untrustedWorld = [];
|
|
329
|
-
const untrustedGroup = [];
|
|
330
|
-
for (const entry of entries) {
|
|
331
|
-
const classification = classifyPrincipal(entry.sid ?? entry.principal, trustedPrincipals);
|
|
332
|
-
if (classification === "trusted")
|
|
333
|
-
trusted.push(entry);
|
|
334
|
-
else if (classification === "world")
|
|
335
|
-
untrustedWorld.push(entry);
|
|
336
|
-
else
|
|
337
|
-
untrustedGroup.push(entry);
|
|
338
|
-
}
|
|
339
|
-
return { trusted, untrustedWorld, untrustedGroup };
|
|
340
|
-
}
|
|
341
|
-
export async function inspectWindowsAcl(targetPath, opts) {
|
|
342
|
-
const exec = opts?.exec ?? defaultPermissionExec;
|
|
343
|
-
try {
|
|
344
|
-
if (opts?.principalTranslationFailed) {
|
|
345
|
-
throw new Error("Windows ACL principal SID translation failed");
|
|
346
|
-
}
|
|
347
|
-
const { stdout, stderr } = await exec(resolveWindowsSystemCommand("icacls.exe", opts?.env), [
|
|
348
|
-
targetPath,
|
|
349
|
-
]);
|
|
350
|
-
let entries = parseIcaclsOutput(`${stdout}\n${stderr}`.trim(), targetPath);
|
|
351
|
-
const unresolvedPrincipals = entries
|
|
352
|
-
.filter((entry) => !entry.sid)
|
|
353
|
-
.map((entry) => entry.principal);
|
|
354
|
-
const principalSids = await resolveWindowsPrincipalSids({
|
|
355
|
-
principals: unresolvedPrincipals,
|
|
356
|
-
known: opts?.principalSids,
|
|
357
|
-
env: opts?.env,
|
|
358
|
-
exec,
|
|
359
|
-
});
|
|
360
|
-
entries = entries.map((entry) => {
|
|
361
|
-
const sid = entry.sid ?? principalSids[entry.principal.toLowerCase()];
|
|
362
|
-
if (!sid) {
|
|
363
|
-
throw new Error(`Windows ACL principal SID could not be verified: ${entry.principal}`);
|
|
364
|
-
}
|
|
365
|
-
return { ...entry, sid };
|
|
366
|
-
});
|
|
367
|
-
let currentUserSid = normalizeSid(opts?.currentUserSid ?? "");
|
|
368
|
-
let effectiveEnv = currentUserSid ? { USERSID: currentUserSid } : undefined;
|
|
369
|
-
let { trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, effectiveEnv);
|
|
370
|
-
const needsUserSidResolution = !currentUserSid && untrustedGroup.some((entry) => entry.sid && !TRUSTED_SIDS.has(entry.sid));
|
|
371
|
-
if (needsUserSidResolution) {
|
|
372
|
-
currentUserSid =
|
|
373
|
-
(await resolveWindowsCurrentUserSid({ exec, env: opts?.env })) ?? "";
|
|
374
|
-
if (currentUserSid) {
|
|
375
|
-
effectiveEnv = { USERSID: currentUserSid };
|
|
376
|
-
({ trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, effectiveEnv));
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
return { ok: true, entries, trusted, untrustedWorld, untrustedGroup };
|
|
380
|
-
}
|
|
381
|
-
catch (err) {
|
|
382
|
-
return {
|
|
383
|
-
ok: false,
|
|
384
|
-
entries: [],
|
|
385
|
-
trusted: [],
|
|
386
|
-
untrustedWorld: [],
|
|
387
|
-
untrustedGroup: [],
|
|
388
|
-
error: String(err),
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
export function formatWindowsAclSummary(summary) {
|
|
393
|
-
if (!summary.ok)
|
|
394
|
-
return "unknown";
|
|
395
|
-
const untrusted = [...summary.untrustedWorld, ...summary.untrustedGroup];
|
|
396
|
-
return untrusted.length === 0
|
|
397
|
-
? "trusted-only"
|
|
398
|
-
: untrusted.map((entry) => `${entry.principal}:${entry.rawRights}`).join(", ");
|
|
399
|
-
}
|
|
400
|
-
export function formatIcaclsResetCommand(targetPath, opts) {
|
|
401
|
-
const command = resolveWindowsSystemCommand("icacls.exe", opts.env);
|
|
402
|
-
const user = resolveWindowsUserPrincipal(opts.env, opts.userInfo) ?? "%USERNAME%";
|
|
403
|
-
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
404
|
-
return [
|
|
405
|
-
command,
|
|
406
|
-
`"${targetPath}"`,
|
|
407
|
-
"/inheritance:r",
|
|
408
|
-
"/grant:r",
|
|
409
|
-
`"${user}:${grant}"`,
|
|
410
|
-
"/grant:r",
|
|
411
|
-
`"*S-1-5-18:${grant}"`,
|
|
412
|
-
].join(" ");
|
|
413
|
-
}
|
|
414
|
-
export function createIcaclsResetCommand(targetPath, opts) {
|
|
415
|
-
const user = resolveWindowsUserPrincipal(opts.env, opts.userInfo);
|
|
416
|
-
if (!user) {
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
420
|
-
const args = [
|
|
421
|
-
targetPath,
|
|
422
|
-
"/inheritance:r",
|
|
423
|
-
"/grant:r",
|
|
424
|
-
`${user}:${grant}`,
|
|
425
|
-
"/grant:r",
|
|
426
|
-
`*S-1-5-18:${grant}`,
|
|
427
|
-
];
|
|
428
|
-
return {
|
|
429
|
-
command: resolveWindowsSystemCommand("icacls.exe", opts.env),
|
|
430
|
-
args,
|
|
431
|
-
display: formatIcaclsResetCommand(targetPath, opts),
|
|
432
|
-
};
|
|
433
|
-
}
|
package/dist/pinned-write.js
CHANGED
|
@@ -69,7 +69,7 @@ export async function runPinnedWriteHelper(params) {
|
|
|
69
69
|
return await runPinnedWriteFallback(params);
|
|
70
70
|
}
|
|
71
71
|
const native = getNativeBinding();
|
|
72
|
-
if (native
|
|
72
|
+
if (native) {
|
|
73
73
|
return await runPinnedWriteNative(native, params);
|
|
74
74
|
}
|
|
75
75
|
return await runPinnedWriteFallback(params);
|
|
@@ -107,6 +107,7 @@ async function runPinnedWriteFallback(params) {
|
|
|
107
107
|
parentPath = await mkdirPathComponentsWithGuards({
|
|
108
108
|
rootReal: params.rootPath,
|
|
109
109
|
targetPath: parentPath,
|
|
110
|
+
beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
|
|
110
111
|
});
|
|
111
112
|
}
|
|
112
113
|
const parentGuard = params.mkdir
|
|
@@ -1 +1 @@
|
|
|
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;AACrC,OAAO,MAAM,MAAM,SAAS,CAAC;AAW7B,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;AAsBD,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,CAyD3C;
|
|
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;AACrC,OAAO,MAAM,MAAM,SAAS,CAAC;AAW7B,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;AAsBD,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,CAyD3C;AA6DD,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,CA6BA;AAmBD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDxF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CA2D7E"}
|
package/dist/regular-file.js
CHANGED
|
@@ -133,9 +133,19 @@ function verifyStableReadTarget(params) {
|
|
|
133
133
|
}
|
|
134
134
|
function readOpenedRegularFileSync(params) {
|
|
135
135
|
const stat = fsSync.fstatSync(params.fd);
|
|
136
|
+
let pathStat;
|
|
137
|
+
try {
|
|
138
|
+
pathStat = fsSync.lstatSync(params.filePath);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (isNotFoundPathError(error)) {
|
|
142
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
136
146
|
verifyStableReadTarget({
|
|
137
147
|
filePath: params.filePath,
|
|
138
|
-
pathStat
|
|
148
|
+
pathStat,
|
|
139
149
|
postOpenStat: stat,
|
|
140
150
|
preOpenStat: params.preOpenStat,
|
|
141
151
|
});
|
|
@@ -168,7 +178,16 @@ export function readRegularFileSync(params) {
|
|
|
168
178
|
if (params.maxBytes !== undefined && result.stat.size > params.maxBytes) {
|
|
169
179
|
throw regularFileTooLargeError(params.filePath, params.maxBytes);
|
|
170
180
|
}
|
|
171
|
-
|
|
181
|
+
let fd;
|
|
182
|
+
try {
|
|
183
|
+
fd = fsSync.openSync(params.filePath, resolveRegularFileReadFlags());
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (isNotFoundPathError(error)) {
|
|
187
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
172
191
|
try {
|
|
173
192
|
return readOpenedRegularFileSync({
|
|
174
193
|
fd,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-directory.d.ts","sourceRoot":"","sources":["../src/replace-directory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replace-directory.d.ts","sourceRoot":"","sources":["../src/replace-directory.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { guardedRename, guardedRm } from "./guarded-mutation.js";
|
|
5
|
+
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
6
|
+
import { serializePathWrite } from "./write-queue.js";
|
|
4
7
|
export async function replaceDirectoryAtomic(options) {
|
|
5
8
|
const targetDir = path.resolve(options.targetDir);
|
|
9
|
+
await serializePathWrite(targetDir, async () => {
|
|
10
|
+
await replaceDirectoryAtomicUnserialized(options, targetDir);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async function replaceDirectoryAtomicUnserialized(options, targetDir) {
|
|
6
14
|
const stagedDir = path.resolve(options.stagedDir);
|
|
7
15
|
const parentDir = path.dirname(targetDir);
|
|
8
|
-
const
|
|
16
|
+
const backupPrefix = assertSafePathPrefix(options.backupPrefix ?? ".fs-safe-dir-backup-", { label: "atomic directory backup prefix" });
|
|
17
|
+
const backupDir = path.join(parentDir, `${backupPrefix}${process.pid}-${randomUUID()}`);
|
|
9
18
|
let backupCreated = false;
|
|
10
19
|
await fs.mkdir(parentDir, { recursive: true });
|
|
11
20
|
try {
|
|
@@ -21,6 +21,7 @@ export declare function copyFallbackReplace(params: {
|
|
|
21
21
|
destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
|
|
22
22
|
restore: ReplaceFileCopyFallbackRestorePolicy;
|
|
23
23
|
maxRestoreBytes?: number;
|
|
24
|
+
sync: boolean;
|
|
24
25
|
}): Promise<void>;
|
|
25
26
|
export declare function copyFallbackReplaceSync(params: {
|
|
26
27
|
fsModule: SyncFallbackFs;
|
|
@@ -29,6 +30,8 @@ export declare function copyFallbackReplaceSync(params: {
|
|
|
29
30
|
destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
|
|
30
31
|
restore: ReplaceFileCopyFallbackRestorePolicy;
|
|
31
32
|
maxRestoreBytes?: number;
|
|
33
|
+
fchmodSync?: (fd: number, mode: number) => void;
|
|
34
|
+
sync: boolean;
|
|
32
35
|
}): void;
|
|
33
36
|
export {};
|
|
34
37
|
//# sourceMappingURL=replace-file-copy-fallback.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAsB,MAAM,SAAS,CAAC;AAK7C,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;
|
|
1
|
+
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAsB,MAAM,SAAS,CAAC;AAK7C,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,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DhB;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,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,IAAI,CA6EP"}
|
|
@@ -189,15 +189,18 @@ function restoreFailure(writeError, cleanup, restoreError) {
|
|
|
189
189
|
? `Atomic copy fallback failed; original destination restored: ${primary.message}`
|
|
190
190
|
: `Atomic copy fallback failed and original restoration failed: ${primary.message}`, { cause, details });
|
|
191
191
|
}
|
|
192
|
-
async function replacePinnedWithRestore(handle, replacement, maxRestoreBytes) {
|
|
192
|
+
async function replacePinnedWithRestore(handle, replacement, maxRestoreBytes, replacementMode) {
|
|
193
|
+
const originalMode = (await handle.stat()).mode;
|
|
193
194
|
const original = await readBounded(handle, maxRestoreBytes);
|
|
194
195
|
try {
|
|
195
196
|
await writeAll(handle, replacement);
|
|
197
|
+
await handle.chmod(replacementMode);
|
|
196
198
|
await handle.sync();
|
|
197
199
|
}
|
|
198
200
|
catch (writeError) {
|
|
199
201
|
try {
|
|
200
202
|
await writeAll(handle, original);
|
|
203
|
+
await handle.chmod(originalMode);
|
|
201
204
|
await handle.sync();
|
|
202
205
|
throw restoreFailure(writeError, "restored");
|
|
203
206
|
}
|
|
@@ -209,15 +212,18 @@ async function replacePinnedWithRestore(handle, replacement, maxRestoreBytes) {
|
|
|
209
212
|
}
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
|
-
function replacePinnedWithRestoreSync(fsModule, fd, replacement, maxRestoreBytes) {
|
|
215
|
+
function replacePinnedWithRestoreSync(fsModule, fd, replacement, maxRestoreBytes, replacementMode, fchmodSync) {
|
|
216
|
+
const originalMode = fsModule.fstatSync(fd).mode;
|
|
213
217
|
const original = readBoundedSync(fsModule, fd, maxRestoreBytes);
|
|
214
218
|
try {
|
|
215
219
|
writeAllSync(fsModule, fd, replacement);
|
|
220
|
+
fchmodSync?.(fd, replacementMode);
|
|
216
221
|
fsModule.fsyncSync(fd);
|
|
217
222
|
}
|
|
218
223
|
catch (writeError) {
|
|
219
224
|
try {
|
|
220
225
|
writeAllSync(fsModule, fd, original);
|
|
226
|
+
fchmodSync?.(fd, originalMode);
|
|
221
227
|
fsModule.fsyncSync(fd);
|
|
222
228
|
throw restoreFailure(writeError, "restored");
|
|
223
229
|
}
|
|
@@ -247,7 +253,7 @@ export async function copyFallbackReplace(params) {
|
|
|
247
253
|
const pinned = await openPinnedDestination(params.fsModule, params.dest, params.destinationHardlinks);
|
|
248
254
|
if (pinned) {
|
|
249
255
|
destHandle = pinned.handle;
|
|
250
|
-
await replacePinnedWithRestore(destHandle, replacement, params.maxRestoreBytes);
|
|
256
|
+
await replacePinnedWithRestore(destHandle, replacement, params.maxRestoreBytes, sourceStat.mode);
|
|
251
257
|
}
|
|
252
258
|
}
|
|
253
259
|
if (!destHandle) {
|
|
@@ -265,13 +271,16 @@ export async function copyFallbackReplace(params) {
|
|
|
265
271
|
}
|
|
266
272
|
destHandle = await params.fsModule.open(params.dest, OPEN_WRITE_EXCLUSIVE_FLAGS, sourceStat.mode & 0o777);
|
|
267
273
|
await destHandle.writeFile(replacement);
|
|
274
|
+
await destHandle.chmod(sourceStat.mode);
|
|
275
|
+
if (params.sync) {
|
|
276
|
+
await destHandle.sync();
|
|
277
|
+
}
|
|
268
278
|
}
|
|
269
279
|
}
|
|
270
280
|
finally {
|
|
271
281
|
await destHandle?.close().catch(() => undefined);
|
|
272
282
|
await sourceHandle.close().catch(() => undefined);
|
|
273
283
|
}
|
|
274
|
-
await params.fsModule.unlink(params.src).catch(() => undefined);
|
|
275
284
|
}
|
|
276
285
|
export function copyFallbackReplaceSync(params) {
|
|
277
286
|
const sourcePreview = params.fsModule.lstatSync(params.src);
|
|
@@ -291,7 +300,7 @@ export function copyFallbackReplaceSync(params) {
|
|
|
291
300
|
const pinned = openPinnedDestinationSync(params.fsModule, params.dest, params.destinationHardlinks);
|
|
292
301
|
if (pinned) {
|
|
293
302
|
destFd = pinned.fd;
|
|
294
|
-
replacePinnedWithRestoreSync(params.fsModule, destFd, replacement, params.maxRestoreBytes);
|
|
303
|
+
replacePinnedWithRestoreSync(params.fsModule, destFd, replacement, params.maxRestoreBytes, sourceStat.mode, params.fchmodSync);
|
|
295
304
|
}
|
|
296
305
|
}
|
|
297
306
|
if (destFd === undefined) {
|
|
@@ -312,6 +321,10 @@ export function copyFallbackReplaceSync(params) {
|
|
|
312
321
|
}
|
|
313
322
|
destFd = params.fsModule.openSync(params.dest, OPEN_WRITE_EXCLUSIVE_FLAGS, sourceStat.mode & 0o777);
|
|
314
323
|
writeAllSync(params.fsModule, destFd, replacement);
|
|
324
|
+
params.fchmodSync?.(destFd, sourceStat.mode);
|
|
325
|
+
if (params.sync) {
|
|
326
|
+
params.fsModule.fsyncSync(destFd);
|
|
327
|
+
}
|
|
315
328
|
}
|
|
316
329
|
}
|
|
317
330
|
finally {
|
|
@@ -330,10 +343,4 @@ export function copyFallbackReplaceSync(params) {
|
|
|
330
343
|
// Best-effort close after fallback replacement.
|
|
331
344
|
}
|
|
332
345
|
}
|
|
333
|
-
try {
|
|
334
|
-
params.fsModule.unlinkSync(params.src);
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
// Best-effort cleanup after fallback replacement.
|
|
338
|
-
}
|
|
339
346
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import syncFs, { type Stats } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
type AsyncTempFileSystem = Pick<typeof fs, "lstat" | "open" | "writeFile">;
|
|
4
|
+
type SyncTempFileSystem = Pick<typeof syncFs, "closeSync" | "fstatSync" | "fsyncSync" | "lstatSync" | "openSync" | "writeFileSync">;
|
|
5
|
+
export type SyncFchmod = (fd: number, mode: number) => void;
|
|
6
|
+
export declare function applyDirectoryMode(params: {
|
|
7
|
+
fsModule: AsyncTempFileSystem;
|
|
8
|
+
dirPath: string;
|
|
9
|
+
mode: number;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
export declare function applyDirectoryModeSync(params: {
|
|
12
|
+
fsModule: SyncTempFileSystem;
|
|
13
|
+
dirPath: string;
|
|
14
|
+
mode: number;
|
|
15
|
+
fchmodSync?: SyncFchmod;
|
|
16
|
+
}): void;
|
|
17
|
+
export declare function writeTempFile(params: {
|
|
18
|
+
fsModule: AsyncTempFileSystem;
|
|
19
|
+
tempPath: string;
|
|
20
|
+
content: string | Uint8Array;
|
|
21
|
+
mode: number;
|
|
22
|
+
sync: boolean;
|
|
23
|
+
}): Promise<Stats>;
|
|
24
|
+
export declare function writeTempFileSync(params: {
|
|
25
|
+
fsModule: SyncTempFileSystem;
|
|
26
|
+
tempPath: string;
|
|
27
|
+
content: string | Uint8Array;
|
|
28
|
+
mode: number;
|
|
29
|
+
fchmodSync?: SyncFchmod;
|
|
30
|
+
sync: boolean;
|
|
31
|
+
}): Stats;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=replace-file-descriptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replace-file-descriptor.d.ts","sourceRoot":"","sources":["../src/replace-file-descriptor.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAuB,MAAM,kBAAkB,CAAC;AAIvD,KAAK,mBAAmB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAC3E,KAAK,kBAAkB,GAAG,IAAI,CAC5B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,eAAe,CACrF,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AA2B5D,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,GAAG,IAAI,CAcP;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CAkBjB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,KAAK,CAkBR"}
|