@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/guarded-mkdir.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
5
|
import { isNotFoundPathError, isPathRelativeEscape } from "./path.js";
|
|
6
|
+
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
6
7
|
function isSameOrChildPath(candidate, parent) {
|
|
7
8
|
const parentPrefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`;
|
|
8
9
|
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
@@ -15,9 +16,7 @@ async function realpathOrThrowNotFile(target) {
|
|
|
15
16
|
if (isNotFoundPathError(error)) {
|
|
16
17
|
// A dangling symlink (or a component removed between lstat and
|
|
17
18
|
// realpath) is not a usable directory component.
|
|
18
|
-
throw
|
|
19
|
-
cause: error instanceof Error ? error : undefined,
|
|
20
|
-
});
|
|
19
|
+
throw directoryComponentNotDirectoryError(error instanceof Error ? error : undefined);
|
|
21
20
|
}
|
|
22
21
|
throw error;
|
|
23
22
|
}
|
|
@@ -40,8 +39,8 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
40
39
|
for (const part of relative.split(path.sep).filter(Boolean)) {
|
|
41
40
|
const next = path.join(current, part);
|
|
42
41
|
const parentGuard = await createAsyncDirectoryGuard(current);
|
|
43
|
-
await params.beforeComponent?.(next);
|
|
44
42
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
43
|
+
await params.beforeComponent?.(next);
|
|
45
44
|
try {
|
|
46
45
|
await fs.mkdir(next);
|
|
47
46
|
}
|
|
@@ -52,7 +51,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
52
51
|
}
|
|
53
52
|
const stat = await fs.lstat(next);
|
|
54
53
|
if (!stat.isSymbolicLink() && !stat.isDirectory()) {
|
|
55
|
-
throw
|
|
54
|
+
throw directoryComponentNotDirectoryError();
|
|
56
55
|
}
|
|
57
56
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
58
57
|
// segment at a time and realpath-check each segment before descending.
|
|
@@ -72,7 +71,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
72
71
|
// the returned resolved path, not their own lexical parent path.
|
|
73
72
|
const targetStat = await fs.stat(nextReal);
|
|
74
73
|
if (!targetStat.isDirectory()) {
|
|
75
|
-
throw
|
|
74
|
+
throw directoryComponentNotDirectoryError();
|
|
76
75
|
}
|
|
77
76
|
await createAsyncDirectoryGuard(nextReal);
|
|
78
77
|
await assertAsyncDirectoryGuard(parentGuard);
|
package/dist/install-path.js
CHANGED
|
@@ -41,7 +41,7 @@ export function resolveSafeInstallDir(params) {
|
|
|
41
41
|
path.isAbsolute(relative)) {
|
|
42
42
|
return { ok: false, error: params.invalidNameMessage };
|
|
43
43
|
}
|
|
44
|
-
return { ok: true, path:
|
|
44
|
+
return { ok: true, path: resolvedTarget };
|
|
45
45
|
}
|
|
46
46
|
export async function assertCanonicalPathWithinBase(params) {
|
|
47
47
|
const baseDir = path.resolve(params.baseDir);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-document-store.d.ts","sourceRoot":"","sources":["../src/json-document-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"json-document-store.d.ts","sourceRoot":"","sources":["../src/json-document-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AASlE,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,IAAI,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC/B,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACvC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE,CAAC;AA2BF,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC5B,OAAO,GAAE,oBAAyB,GACjC,SAAS,CAAC,CAAC,CAAC,CAiFd"}
|
|
@@ -43,24 +43,30 @@ export function createJsonStore(adapter, options = {}) {
|
|
|
43
43
|
async function withSerializedMutation(run) {
|
|
44
44
|
const canonicalPath = await canonicalPathFromExistingAncestor(adapter.filePath);
|
|
45
45
|
const activePaths = activeStoreMutations.getStore();
|
|
46
|
-
if (activePaths?.
|
|
46
|
+
if (activePaths?.get(canonicalPath)?.active) {
|
|
47
47
|
throw new FsSafeError("store-reentrant-update", `jsonStore cannot write or update ${canonicalPath} from inside its active update callback; return the complete next value from the outer update instead`);
|
|
48
48
|
}
|
|
49
49
|
return await serializePathWrite(`json-store:${canonicalPath}`, async () => {
|
|
50
|
-
const mutationPaths = new
|
|
51
|
-
|
|
50
|
+
const mutationPaths = new Map([...(activePaths ?? [])].filter(([, token]) => token.active));
|
|
51
|
+
const token = { active: true };
|
|
52
|
+
mutationPaths.set(canonicalPath, token);
|
|
52
53
|
return await activeStoreMutations.run(mutationPaths, async () => {
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
try {
|
|
55
|
+
if (!locks || !lockOptions) {
|
|
56
|
+
return await run();
|
|
57
|
+
}
|
|
58
|
+
return await locks.withLock({
|
|
59
|
+
targetPath: adapter.filePath,
|
|
60
|
+
staleMs: lockOptions.staleMs,
|
|
61
|
+
timeoutMs: lockOptions.timeoutMs,
|
|
62
|
+
retry: lockOptions.retry,
|
|
63
|
+
staleRecovery: lockOptions.staleRecovery,
|
|
64
|
+
payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
|
|
65
|
+
}, run);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
token.active = false;
|
|
55
69
|
}
|
|
56
|
-
return await locks.withLock({
|
|
57
|
-
targetPath: adapter.filePath,
|
|
58
|
-
staleMs: lockOptions.staleMs,
|
|
59
|
-
timeoutMs: lockOptions.timeoutMs,
|
|
60
|
-
retry: lockOptions.retry,
|
|
61
|
-
staleRecovery: lockOptions.staleRecovery,
|
|
62
|
-
payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
|
|
63
|
-
}, run);
|
|
64
70
|
});
|
|
65
71
|
});
|
|
66
72
|
}
|
package/dist/json.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAM7B,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
|
package/dist/json.js
CHANGED
|
@@ -7,6 +7,7 @@ import { stringifyJsonDocument } from "./json-stringify.js";
|
|
|
7
7
|
import { readRegularFile, readRegularFileSync, statRegularFile } from "./regular-file.js";
|
|
8
8
|
import { openRootFileSync } from "./root-file.js";
|
|
9
9
|
import { writeTextAtomic } from "./text-atomic.js";
|
|
10
|
+
import { sleep } from "./timing.js";
|
|
10
11
|
const READ_RETRY_MAX_ATTEMPTS = 5;
|
|
11
12
|
const READ_RETRY_BASE_DELAY_MS = 50;
|
|
12
13
|
function isRetryableReadError(err, options) {
|
|
@@ -19,9 +20,6 @@ function isRetryableReadError(err, options) {
|
|
|
19
20
|
const code = getErrorCode(err);
|
|
20
21
|
return code === "ENOENT" || code === "EPERM";
|
|
21
22
|
}
|
|
22
|
-
function sleep(ms) {
|
|
23
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24
|
-
}
|
|
25
23
|
async function readRegularFileWithRetry(filePath, options = {}) {
|
|
26
24
|
let lastErr;
|
|
27
25
|
for (let attempt = 0; attempt < READ_RETRY_MAX_ATTEMPTS; attempt++) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
export declare function isFileUrl(input: string): boolean;
|
|
1
2
|
export declare function hasEncodedFileUrlSeparator(pathname: string): boolean;
|
|
2
3
|
export declare function isWindowsNetworkPath(filePath: string, platform?: NodeJS.Platform): boolean;
|
|
3
4
|
export declare function isWindowsDriveLetterPath(filePath: string, platform?: NodeJS.Platform): boolean;
|
|
4
5
|
export declare function assertNoWindowsNetworkPath(filePath: string, label?: string): void;
|
|
5
|
-
export declare function safeFileURLToPath(fileUrl: string): string;
|
|
6
|
-
export declare function trySafeFileURLToPath(fileUrl: string): string | undefined;
|
|
6
|
+
export declare function safeFileURLToPath(fileUrl: string, platform?: NodeJS.Platform): string;
|
|
7
|
+
export declare function trySafeFileURLToPath(fileUrl: string, platform?: NodeJS.Platform): string | undefined;
|
|
7
8
|
export declare function basenameFromMediaSource(source?: string): string | undefined;
|
|
8
9
|
//# sourceMappingURL=local-file-access.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"AAOA,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEhD;AAOD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAeT;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAET;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,IAAI,CAIjF;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,CAqBR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB3E"}
|
|
@@ -2,6 +2,10 @@ import path from "node:path";
|
|
|
2
2
|
import { fileURLToPath, URL } from "node:url";
|
|
3
3
|
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
4
4
|
const ENCODED_FILE_URL_SEPARATOR_RE = /%(?:2f|5c)/i;
|
|
5
|
+
const FILE_URL_PREFIX_RE = /^file:\/\//i;
|
|
6
|
+
export function isFileUrl(input) {
|
|
7
|
+
return FILE_URL_PREFIX_RE.test(input);
|
|
8
|
+
}
|
|
5
9
|
function isLocalFileUrlHost(hostname) {
|
|
6
10
|
const normalized = normalizeLowercaseStringOrEmpty(hostname);
|
|
7
11
|
return normalized === "" || normalized === "localhost";
|
|
@@ -32,7 +36,7 @@ export function assertNoWindowsNetworkPath(filePath, label = "Path") {
|
|
|
32
36
|
throw new Error(`${label} cannot use Windows network paths: ${filePath}`);
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
|
-
export function safeFileURLToPath(fileUrl) {
|
|
39
|
+
export function safeFileURLToPath(fileUrl, platform = process.platform) {
|
|
36
40
|
let parsed;
|
|
37
41
|
try {
|
|
38
42
|
parsed = new URL(fileUrl);
|
|
@@ -49,13 +53,15 @@ export function safeFileURLToPath(fileUrl) {
|
|
|
49
53
|
if (hasEncodedFileUrlSeparator(parsed.pathname)) {
|
|
50
54
|
throw new Error(`file:// URLs cannot encode path separators: ${fileUrl}`);
|
|
51
55
|
}
|
|
52
|
-
const filePath = fileURLToPath(parsed);
|
|
53
|
-
|
|
56
|
+
const filePath = fileURLToPath(parsed, { windows: platform === "win32" });
|
|
57
|
+
if (isWindowsNetworkPath(filePath, platform)) {
|
|
58
|
+
throw new Error(`Local file URL cannot use Windows network paths: ${filePath}`);
|
|
59
|
+
}
|
|
54
60
|
return filePath;
|
|
55
61
|
}
|
|
56
|
-
export function trySafeFileURLToPath(fileUrl) {
|
|
62
|
+
export function trySafeFileURLToPath(fileUrl, platform = process.platform) {
|
|
57
63
|
try {
|
|
58
|
-
return safeFileURLToPath(fileUrl);
|
|
64
|
+
return safeFileURLToPath(fileUrl, platform);
|
|
59
65
|
}
|
|
60
66
|
catch {
|
|
61
67
|
return undefined;
|
|
@@ -65,7 +71,7 @@ export function basenameFromMediaSource(source) {
|
|
|
65
71
|
if (!source) {
|
|
66
72
|
return undefined;
|
|
67
73
|
}
|
|
68
|
-
if (source
|
|
74
|
+
if (isFileUrl(source)) {
|
|
69
75
|
const filePath = trySafeFileURLToPath(source);
|
|
70
76
|
return filePath ? path.basename(filePath) || undefined : undefined;
|
|
71
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;
|
|
1
|
+
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAqGF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA6B7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA2CtC"}
|
package/dist/local-roots.js
CHANGED
|
@@ -2,11 +2,11 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { expandHomePrefix, resolveUserPath } from "./home-dir.js";
|
|
5
|
-
import { safeFileURLToPath } from "./local-file-access.js";
|
|
5
|
+
import { isFileUrl, safeFileURLToPath } from "./local-file-access.js";
|
|
6
6
|
import { isPathInside } from "./path.js";
|
|
7
7
|
import { root } from "./root.js";
|
|
8
8
|
function resolveLocalPathInput(input, label) {
|
|
9
|
-
if (input
|
|
9
|
+
if (isFileUrl(input)) {
|
|
10
10
|
try {
|
|
11
11
|
return safeFileURLToPath(input);
|
|
12
12
|
}
|
|
@@ -25,7 +25,7 @@ function resolveLocalRootInput(input, label) {
|
|
|
25
25
|
if (!trimmed) {
|
|
26
26
|
throw new FsSafeError("invalid-path", `${label} entry is required`);
|
|
27
27
|
}
|
|
28
|
-
const resolved = trimmed
|
|
28
|
+
const resolved = isFileUrl(trimmed)
|
|
29
29
|
? resolveLocalPathInput(trimmed, label)
|
|
30
30
|
: expandHomePrefix(trimmed);
|
|
31
31
|
if (resolved.includes("\0")) {
|
|
@@ -41,7 +41,9 @@ function isPathInsideRoot(candidate, rootDir) {
|
|
|
41
41
|
}
|
|
42
42
|
function resolveRootRealSync(rootDir) {
|
|
43
43
|
try {
|
|
44
|
-
|
|
44
|
+
// Configured roots may themselves be symlinks. Follow only this trusted
|
|
45
|
+
// root entry, then use its canonical directory for containment checks.
|
|
46
|
+
const stat = fsSync.statSync(rootDir);
|
|
45
47
|
if (!stat.isDirectory()) {
|
|
46
48
|
return null;
|
|
47
49
|
}
|
|
@@ -101,8 +103,8 @@ function resolveCandidateCanonicalSync(filePath) {
|
|
|
101
103
|
export function resolveLocalPathFromRootsSync(options) {
|
|
102
104
|
const label = options.label ?? "local roots";
|
|
103
105
|
const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
|
|
107
|
+
for (const rootDir of rootDirs) {
|
|
106
108
|
const rootReal = resolveRootRealSync(rootDir);
|
|
107
109
|
if (!rootReal) {
|
|
108
110
|
continue;
|
|
@@ -129,8 +131,8 @@ export function resolveLocalPathFromRootsSync(options) {
|
|
|
129
131
|
export async function readLocalFileFromRoots(options) {
|
|
130
132
|
const label = options.label ?? "local roots";
|
|
131
133
|
const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
|
|
135
|
+
for (const rootDir of rootDirs) {
|
|
134
136
|
let scopedRoot;
|
|
135
137
|
try {
|
|
136
138
|
scopedRoot = await root(rootDir);
|
|
@@ -138,29 +140,32 @@ export async function readLocalFileFromRoots(options) {
|
|
|
138
140
|
catch {
|
|
139
141
|
continue;
|
|
140
142
|
}
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
143
|
+
const readOptions = {
|
|
144
|
+
hardlinks: options.hardlinks,
|
|
145
|
+
nonBlockingRead: options.nonBlockingRead,
|
|
146
|
+
symlinks: options.symlinks,
|
|
147
|
+
};
|
|
148
|
+
// Leave maxBytes absent when the caller omits it so Root's own default
|
|
149
|
+
// cap remains in force instead of being overwritten by undefined.
|
|
150
|
+
if (options.maxBytes !== undefined) {
|
|
151
|
+
readOptions.maxBytes = options.maxBytes;
|
|
152
|
+
}
|
|
153
|
+
// A trusted root symlink has two valid spellings. Preserve the caller's
|
|
154
|
+
// lexical spelling when possible so Root.read() still enforces its
|
|
155
|
+
// symlink policy, while also accepting a path expressed below rootReal.
|
|
156
|
+
const relativePaths = [scopedRoot.rootDir, scopedRoot.rootReal]
|
|
157
|
+
.filter((rootPath, index, roots) => roots.indexOf(rootPath) === index)
|
|
158
|
+
.filter((rootPath) => isPathInside(rootPath, requestedPath))
|
|
159
|
+
.map((rootPath) => path.relative(rootPath, requestedPath))
|
|
160
|
+
.filter(Boolean);
|
|
161
|
+
for (const relativePath of relativePaths) {
|
|
162
|
+
try {
|
|
163
|
+
const result = await scopedRoot.read(relativePath, readOptions);
|
|
164
|
+
return { ...result, root: scopedRoot.rootReal };
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Try the canonical spelling before moving to the next configured root.
|
|
158
168
|
}
|
|
159
|
-
const result = await scopedRoot.read(relativePath, readOptions);
|
|
160
|
-
return { ...result, root: scopedRoot.rootReal };
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
continue;
|
|
164
169
|
}
|
|
165
170
|
}
|
|
166
171
|
return null;
|
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":"
|
|
1
|
+
{"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,+BAA+B,GAAG;IAC5C,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;AA6VD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CA2Df"}
|
package/dist/move-path.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { guardedRename } from "./guarded-mutation.js";
|
|
7
|
+
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
7
8
|
export function moveCopyFallbackReasonForRenameError(error, platform = process.platform) {
|
|
8
9
|
const code = error?.code;
|
|
9
10
|
if (code === "EXDEV") {
|
|
@@ -102,6 +103,21 @@ function regularReadFlags() {
|
|
|
102
103
|
? fsConstants.O_NONBLOCK
|
|
103
104
|
: 0));
|
|
104
105
|
}
|
|
106
|
+
async function chmodDirectoryPinned(directoryPath, mode) {
|
|
107
|
+
if (process.platform === "win32") {
|
|
108
|
+
// Node cannot portably open a Windows directory for descriptor-bound
|
|
109
|
+
// chmod. POSIX modes are not enforced there, so do not fall back to a
|
|
110
|
+
// pathname operation that could follow a replacement symlink.
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const handle = await fs.open(directoryPath, fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW);
|
|
114
|
+
try {
|
|
115
|
+
await handle.chmod(mode);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
await handle.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
105
121
|
async function writeAll(handle, buffer, bytesRead) {
|
|
106
122
|
let offset = 0;
|
|
107
123
|
while (offset < bytesRead) {
|
|
@@ -141,20 +157,20 @@ async function copyRegularFilePinned(params) {
|
|
|
141
157
|
}
|
|
142
158
|
await writeAll(destinationHandle, scratch, bytesRead);
|
|
143
159
|
}
|
|
160
|
+
// Re-check the opened source before the staged tree can be committed. If
|
|
161
|
+
// it changed while we copied, the caller should retry the move.
|
|
162
|
+
const finalSourceStat = await sourceHandle.stat();
|
|
163
|
+
if (params.rejectHardlinks && finalSourceStat.nlink > 1) {
|
|
164
|
+
throw hardlinkedSourceError(params.from);
|
|
165
|
+
}
|
|
166
|
+
if (!sameIdentity(params.identity, entryIdentity(finalSourceStat))) {
|
|
167
|
+
throw sourceChangedError(params.from);
|
|
168
|
+
}
|
|
169
|
+
await destinationHandle.chmod(modeBits(params.mode));
|
|
144
170
|
}
|
|
145
171
|
finally {
|
|
146
172
|
await destinationHandle.close();
|
|
147
173
|
}
|
|
148
|
-
// Re-check the opened handle before the staged tree can be committed. If
|
|
149
|
-
// the source changed while we copied, the caller should retry the move.
|
|
150
|
-
const finalSourceStat = await sourceHandle.stat();
|
|
151
|
-
if (params.rejectHardlinks && finalSourceStat.nlink > 1) {
|
|
152
|
-
throw hardlinkedSourceError(params.from);
|
|
153
|
-
}
|
|
154
|
-
if (!sameIdentity(params.identity, entryIdentity(finalSourceStat))) {
|
|
155
|
-
throw sourceChangedError(params.from);
|
|
156
|
-
}
|
|
157
|
-
await fs.chmod(params.to, modeBits(params.mode)).catch(() => undefined);
|
|
158
174
|
}
|
|
159
175
|
catch (error) {
|
|
160
176
|
if (destinationCreated) {
|
|
@@ -198,7 +214,7 @@ async function copyEntryWithManifest(from, to, options) {
|
|
|
198
214
|
await assertSourceStillMatches(from, identity);
|
|
199
215
|
// mkdir() honors process umask. Restore the source mode before commit so
|
|
200
216
|
// EXDEV fallback preserves directory permissions like fs.cp did.
|
|
201
|
-
await
|
|
217
|
+
await chmodDirectoryPinned(to, modeBits(sourceStat.mode));
|
|
202
218
|
return { ...identity, children, kind: "directory" };
|
|
203
219
|
}
|
|
204
220
|
if (!sourceStat.isFile()) {
|
|
@@ -284,19 +300,42 @@ export async function movePathWithCopyFallback(options) {
|
|
|
284
300
|
await assertCopyDestinationOutsideSource(sourcePath, targetPath);
|
|
285
301
|
const targetDir = path.dirname(targetPath);
|
|
286
302
|
const staged = path.join(targetDir, `.fs-safe-move-${process.pid}-${randomUUID()}.tmp`);
|
|
303
|
+
const unregisterStaged = registerTempPathForExit(staged, { recursive: true });
|
|
304
|
+
let stagedCommitted = false;
|
|
287
305
|
try {
|
|
288
306
|
const manifest = await copyEntryWithManifest(sourcePath, staged, {
|
|
289
307
|
sourceHardlinks: options.sourceHardlinks ?? "allow",
|
|
290
308
|
...(rejectHardlinks ? { budget: { discovered: 1 } } : {}),
|
|
291
309
|
});
|
|
310
|
+
unregisterStaged.setIdentity(await fs.lstat(staged));
|
|
292
311
|
await assertCopyDestinationOutsideSource(sourcePath, targetPath);
|
|
293
312
|
await guardedRename({ from: staged, to: targetPath });
|
|
313
|
+
stagedCommitted = true;
|
|
314
|
+
unregisterStaged();
|
|
294
315
|
const cleanupResult = await cleanupCopiedEntry(sourcePath, manifest);
|
|
295
316
|
if (cleanupResult === "stale") {
|
|
296
317
|
throw sourceChangedError(sourcePath);
|
|
297
318
|
}
|
|
298
319
|
}
|
|
299
320
|
finally {
|
|
300
|
-
|
|
321
|
+
if (!stagedCommitted) {
|
|
322
|
+
try {
|
|
323
|
+
const stagedIdentity = await fs.lstat(staged);
|
|
324
|
+
if (!stagedIdentity.isSymbolicLink())
|
|
325
|
+
unregisterStaged.setIdentity(stagedIdentity);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
if (error.code === "ENOENT") {
|
|
329
|
+
unregisterStaged();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
await fs.rm(staged, { recursive: true, force: true });
|
|
334
|
+
unregisterStaged();
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
// Keep the identity-bound exit cleanup registered for a later retry.
|
|
338
|
+
}
|
|
339
|
+
}
|
|
301
340
|
}
|
|
302
341
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/native-binding.d.ts
CHANGED
|
@@ -74,6 +74,7 @@ export interface NativeBinding {
|
|
|
74
74
|
readArchiveEntryNative(path: string, kind: string, requested: string, maxBytes: number, maxEntries: number, maxMetaEntryBytes: number, signal: AbortSignal): Promise<Buffer>;
|
|
75
75
|
readOwnerAndDacl(path: string): NativeWindowsSecurityFacts;
|
|
76
76
|
renameNoReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
|
|
77
|
+
renameReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
|
|
77
78
|
sha256File(fd: number): Promise<NativeFileHash>;
|
|
78
79
|
}
|
|
79
80
|
//# sourceMappingURL=native-binding.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AA0CF,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB,GAAG,IAAI,CAsBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AA0CF,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB,GAAG,IAAI,CAsBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAyDvC;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQzD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5D"}
|
|
@@ -72,7 +72,20 @@ export async function createNativeExclusiveFile(targetPath, mode) {
|
|
|
72
72
|
let fd;
|
|
73
73
|
let created;
|
|
74
74
|
try {
|
|
75
|
-
|
|
75
|
+
let opened;
|
|
76
|
+
try {
|
|
77
|
+
opened = binding.openBeneath(parent.fd, basename, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// The parent open above and every post-create operation remain untagged.
|
|
81
|
+
// Only this exclusive-open failure has enough provenance for a caller to
|
|
82
|
+
// classify a Windows lock-file denial without swallowing setup failures.
|
|
83
|
+
const openError = error;
|
|
84
|
+
if (process.platform === "win32" && openError.code === "EPERM") {
|
|
85
|
+
openError.path = targetPath;
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
76
89
|
fd = opened.fd;
|
|
77
90
|
fsSync.fchmodSync(fd, mode);
|
|
78
91
|
created = fsSync.fstatSync(fd);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAiD7E,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAiD7E,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAsH3B"}
|
|
@@ -9,23 +9,22 @@ function assertWithinMaxBytes(bytes, maxBytes) {
|
|
|
9
9
|
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
async function
|
|
12
|
+
async function writeNativeInput(fd, input, maxBytes) {
|
|
13
13
|
if (input.kind === "buffer") {
|
|
14
14
|
const data = typeof input.data === "string"
|
|
15
15
|
? Buffer.from(input.data, input.encoding ?? "utf8")
|
|
16
16
|
: Buffer.from(input.data);
|
|
17
17
|
assertWithinMaxBytes(data.byteLength, maxBytes);
|
|
18
|
-
|
|
18
|
+
writeNativeFd(fd, data);
|
|
19
|
+
return;
|
|
19
20
|
}
|
|
20
|
-
const chunks = [];
|
|
21
21
|
let bytes = 0;
|
|
22
22
|
for await (const chunk of input.stream) {
|
|
23
23
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
24
24
|
bytes += buffer.byteLength;
|
|
25
25
|
assertWithinMaxBytes(bytes, maxBytes);
|
|
26
|
-
|
|
26
|
+
writeNativeFd(fd, buffer);
|
|
27
27
|
}
|
|
28
|
-
return Buffer.concat(chunks, bytes);
|
|
29
28
|
}
|
|
30
29
|
function nativeOpenFlags(flags) {
|
|
31
30
|
const closeOnExec = fsSync.constants.O_CLOEXEC;
|
|
@@ -37,7 +36,6 @@ function sameNativeIdentity(left, right) {
|
|
|
37
36
|
return left.dev === right.dev && left.ino === right.ino;
|
|
38
37
|
}
|
|
39
38
|
export async function runPinnedWriteNative(binding, params) {
|
|
40
|
-
const data = await inputToBuffer(params.input, params.maxBytes);
|
|
41
39
|
const root = await fs.open(params.rootPath, fsSync.constants.O_RDONLY |
|
|
42
40
|
(typeof fsSync.constants.O_DIRECTORY === "number" ? fsSync.constants.O_DIRECTORY : 0));
|
|
43
41
|
let parentFd;
|
|
@@ -67,27 +65,56 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
67
65
|
!sameNativeIdentity(parentPathStat, parentIdentity)) {
|
|
68
66
|
throw new FsSafeError("path-mismatch", "native write parent changed during resolution");
|
|
69
67
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
if (params.overwrite === false) {
|
|
69
|
+
try {
|
|
70
|
+
await fs.lstat(path.join(parentPath, params.basename));
|
|
71
|
+
throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code !== "ENOENT") {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
|
|
80
|
-
fsSync.fchmodSync(tempFd, params.mode || 0o600);
|
|
81
|
-
writeNativeFd(tempFd, data);
|
|
82
|
-
syncNativeFileBestEffort(tempFd);
|
|
83
80
|
tempIdentity = fsSync.fstatSync(tempFd);
|
|
84
|
-
binding
|
|
81
|
+
// Creation is requested at 0600 in the binding, but a restrictive umask
|
|
82
|
+
// can remove owner access. Keep the unpublished inode private and
|
|
83
|
+
// reopenable until the published name has been identity-fenced.
|
|
84
|
+
fsSync.fchmodSync(tempFd, 0o600);
|
|
85
|
+
await writeNativeInput(tempFd, params.input, params.maxBytes);
|
|
86
|
+
syncNativeFileBestEffort(tempFd);
|
|
87
|
+
if (params.overwrite === false) {
|
|
88
|
+
binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
binding.renameReplace(parentFd, tempName, parentFd, params.basename);
|
|
92
|
+
}
|
|
85
93
|
renamed = true;
|
|
86
94
|
targetFd = binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
|
|
87
95
|
const targetIdentity = binding.fstatIdentity(targetFd);
|
|
88
96
|
if (!targetIdentity.isFile || !sameNativeIdentity(tempIdentity, targetIdentity)) {
|
|
89
97
|
throw new FsSafeError("path-mismatch", "native write target changed after rename");
|
|
90
98
|
}
|
|
99
|
+
// Native exclusive creation starts at 0600. Apply the requested mode only
|
|
100
|
+
// after reopening and fencing the published name, both so mode 000 stays
|
|
101
|
+
// verifiable and so broader modes are never exposed before that fence.
|
|
102
|
+
try {
|
|
103
|
+
fsSync.fchmodSync(targetFd, params.mode);
|
|
104
|
+
syncNativeFileBestEffort(targetFd);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
fsSync.closeSync(targetFd);
|
|
108
|
+
targetFd = undefined;
|
|
109
|
+
removeNativeCreatedFileIfStillPinned({
|
|
110
|
+
binding,
|
|
111
|
+
parentPath,
|
|
112
|
+
parentFd,
|
|
113
|
+
basename: params.basename,
|
|
114
|
+
created: tempIdentity,
|
|
115
|
+
});
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
91
118
|
syncNativeFileBestEffort(parentFd);
|
|
92
119
|
return { dev: targetIdentity.dev, ino: targetIdentity.ino };
|
|
93
120
|
}
|