@openclaw/fs-safe 0.18.1 → 0.19.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 +28 -0
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.js +1 -1
- package/dist/archive-durability.js +1 -1
- package/dist/archive-merge.js +1 -2
- package/dist/archive-staging.d.ts +1 -2
- package/dist/archive-staging.js +3 -3
- package/dist/archive-zip-preflight.d.ts +0 -2
- package/dist/archive-zip-preflight.js +0 -1
- package/dist/archive.d.ts +6 -3
- package/dist/archive.js +5 -3
- package/dist/directory-guard.d.ts +0 -1
- package/dist/directory-guard.js +0 -3
- package/dist/directory-mode-node.d.ts +20 -2
- package/dist/directory-mode-node.js +77 -1
- package/dist/file-lock-sync-acquisition.js +1 -1
- package/dist/file-lock-sync-root-held.d.ts +4 -11
- package/dist/file-lock-sync-root-io.d.ts +1 -4
- package/dist/file-lock-sync-root-options.d.ts +2 -10
- package/dist/file-lock-sync-root.d.ts +2 -4
- package/dist/file-store-boundary.d.ts +3 -7
- package/dist/file-store-boundary.js +7 -10
- package/dist/file-store-prune.js +3 -2
- package/dist/file-store.js +19 -21
- package/dist/guest-native-python.js +23 -31
- package/dist/guest.js +21 -12
- package/dist/json-durable-queue-ownership.d.ts +1 -5
- package/dist/json-durable-queue.d.ts +0 -1
- package/dist/json-durable-queue.js +2 -7
- package/dist/local-file-descriptor.d.ts +2 -5
- package/dist/local-roots.d.ts +2 -7
- package/dist/native-binding.d.ts +12 -13
- package/dist/permission-exec.d.ts +6 -0
- package/dist/permissions-public.d.ts +2 -1
- package/dist/permissions-windows.d.ts +3 -6
- package/dist/permissions.d.ts +3 -10
- package/dist/permissions.js +0 -1
- package/dist/pinned-mutation-shared-route.d.ts +2 -8
- package/dist/pinned-open.d.ts +1 -0
- package/dist/pinned-open.js +1 -1
- package/dist/replace-directory.js +5 -17
- package/dist/replace-file-copy-fallback.d.ts +1 -8
- package/dist/replace-file-descriptor.d.ts +3 -11
- package/dist/replace-file-descriptor.js +1 -1
- package/dist/retained-directory-replacement.d.ts +1 -0
- package/dist/retained-directory-replacement.js +1 -1
- package/dist/root-context.js +3 -2
- package/dist/root-file-final-admission.d.ts +1 -1
- package/dist/root-file-final-admission.js +1 -6
- package/dist/root-file.js +2 -5
- package/dist/root-move-noreplace.d.ts +2 -7
- package/dist/root-paths.d.ts +2 -6
- package/dist/root-remove-identity.d.ts +1 -3
- package/dist/root-walk.js +8 -3
- package/dist/root-write-admission.js +1 -4
- package/dist/root-write-complete-parent.d.ts +2 -0
- package/dist/root-write-complete-parent.js +1 -1
- package/dist/secret-file.d.ts +6 -2
- package/dist/secret-file.js +12 -16
- package/dist/secret-read-policy.js +1 -1
- package/dist/secure-file-windows.js +5 -14
- package/dist/secure-temp-dir.js +2 -7
- package/dist/sidecar-lock-acquire.js +1 -1
- package/dist/sidecar-lock-admission-parser.d.ts +1 -2
- package/dist/sidecar-lock-handle.d.ts +2 -8
- package/dist/sidecar-lock-policy.d.ts +2 -7
- package/dist/sidecar-lock-reclaim.js +5 -6
- package/dist/sidecar-lock-stale-admission.d.ts +1 -5
- package/dist/stat-observation.js +3 -10
- package/dist/strict-file-identity.d.ts +2 -0
- package/dist/strict-file-identity.js +6 -6
- package/dist/temp-target.js +3 -8
- package/dist/temp-workspace-admission.js +6 -12
- package/dist/temp-workspace-child-admission.js +2 -8
- package/dist/temp-workspace-descriptor.js +1 -2
- package/dist/test-hooks.d.ts +1 -1
- package/dist/windows-owner.d.ts +3 -6
- package/dist/windows-security-bridge.cs +0 -2
- package/dist/windows-security-command.js +1 -4
- package/dist/windows-security-facts.d.ts +1 -0
- package/dist/windows-security-facts.js +2 -2
- package/docs/archive.md +2 -0
- package/docs/copy.md +2 -0
- package/docs/file-store.md +5 -0
- package/docs/guest.md +7 -1
- package/docs/native-helper.md +6 -0
- package/docs/root.md +10 -1
- package/docs/secret-file.md +19 -0
- package/docs/sidecar-lock.md +2 -0
- package/docs/walk.md +12 -0
- package/docs/writing.md +11 -0
- package/package.json +8 -8
- package/dist/directory-mode-owner.d.ts +0 -20
- package/dist/directory-mode-owner.js +0 -78
package/dist/file-store.js
CHANGED
|
@@ -3,7 +3,7 @@ import { normalizeMaxBytes } from "./byte-budget.js";
|
|
|
3
3
|
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
5
|
import { pruneExpiredStoreEntries } from "./file-store-prune.js";
|
|
6
|
-
import { assertFileStoreMaxBytes, assertRelativePath, ensureParentInRoot, openPrivateStoreLockRoot, openWritableStoreRoot, readFileStoreCopySource, resolveStorePath, writeStreamToTempSource, } from "./file-store-boundary.js";
|
|
6
|
+
import { assertFileStoreMaxBytes, assertRelativePath, ensureParentInRoot, literalStoreRootPath, openPrivateStoreLockRoot, openWritableStoreRoot, readFileStoreCopySource, resolveStorePath, writeStreamToTempSource, } from "./file-store-boundary.js";
|
|
7
7
|
import { writeFileSyncAtomic } from "./file-store-sync-write.js";
|
|
8
8
|
import { createJsonStore } from "./json-document-store.js";
|
|
9
9
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
@@ -61,7 +61,7 @@ function handleSyncStoreReadOpenFailure(opened) {
|
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
async function copyIntoRoot(params) {
|
|
64
|
-
const relativePath =
|
|
64
|
+
const relativePath = params.relativePath;
|
|
65
65
|
const destination = resolveStorePath(params.rootDir, relativePath);
|
|
66
66
|
assertNoWindowsPathAlias(params.sourcePath, "filesystem", "source path uses a Windows filesystem namespace alias");
|
|
67
67
|
const sourceStat = syncFs.lstatSync(params.sourcePath);
|
|
@@ -75,7 +75,7 @@ async function copyIntoRoot(params) {
|
|
|
75
75
|
maxBytes: params.maxBytes,
|
|
76
76
|
});
|
|
77
77
|
await ensureParentInRoot(scopedRoot, relativePath, params.dirMode);
|
|
78
|
-
await scopedRoot.copyIn(relativePath, params.sourcePath, {
|
|
78
|
+
await scopedRoot.copyIn(literalStoreRootPath(relativePath), params.sourcePath, {
|
|
79
79
|
durable: params.durable,
|
|
80
80
|
maxBytes: params.maxBytes,
|
|
81
81
|
mkdir: false,
|
|
@@ -97,8 +97,7 @@ export function fileStore(options) {
|
|
|
97
97
|
return await root(rootDir, { hardlinks: "reject", maxBytes });
|
|
98
98
|
}
|
|
99
99
|
async function write(relativePath, data, writeOptions) {
|
|
100
|
-
const
|
|
101
|
-
const destination = resolveStorePath(rootDir, safeRelativePath);
|
|
100
|
+
const destination = resolveStorePath(rootDir, relativePath);
|
|
102
101
|
const content = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
103
102
|
const writeMaxBytes = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
|
|
104
103
|
assertFileStoreMaxBytes(content.byteLength, writeMaxBytes);
|
|
@@ -121,8 +120,8 @@ export function fileStore(options) {
|
|
|
121
120
|
dirMode: writeDirMode,
|
|
122
121
|
maxBytes: writeMaxBytes,
|
|
123
122
|
});
|
|
124
|
-
await ensureParentInRoot(scopedRoot,
|
|
125
|
-
await scopedRoot.write(
|
|
123
|
+
await ensureParentInRoot(scopedRoot, relativePath, writeDirMode);
|
|
124
|
+
await scopedRoot.write(literalStoreRootPath(relativePath), content, {
|
|
126
125
|
mkdir: false,
|
|
127
126
|
mode: writeMode,
|
|
128
127
|
durable: writeDurable,
|
|
@@ -135,8 +134,7 @@ export function fileStore(options) {
|
|
|
135
134
|
root: openRoot,
|
|
136
135
|
write,
|
|
137
136
|
writeStream: async (relativePath, stream, writeOptions) => {
|
|
138
|
-
const
|
|
139
|
-
const destination = resolveStorePath(rootDir, safeRelativePath);
|
|
137
|
+
const destination = resolveStorePath(rootDir, relativePath);
|
|
140
138
|
const configuredLimit = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
|
|
141
139
|
const limit = configuredLimit ?? (privateMode ? DEFAULT_ROOT_MAX_BYTES : undefined);
|
|
142
140
|
if (privateMode) {
|
|
@@ -172,7 +170,7 @@ export function fileStore(options) {
|
|
|
172
170
|
try {
|
|
173
171
|
await copyIntoRoot({
|
|
174
172
|
rootDir,
|
|
175
|
-
relativePath
|
|
173
|
+
relativePath,
|
|
176
174
|
sourcePath: staged.path,
|
|
177
175
|
durable: writeDurable,
|
|
178
176
|
maxBytes: limit,
|
|
@@ -214,17 +212,17 @@ export function fileStore(options) {
|
|
|
214
212
|
tempPrefix: writeOptions?.tempPrefix,
|
|
215
213
|
});
|
|
216
214
|
},
|
|
217
|
-
open: async (relativePath, readOptions) => await (await openRoot()).open(assertRelativePath(relativePath), readOptions),
|
|
218
|
-
read: async (relativePath, readOptions) => await (await openRoot()).read(assertRelativePath(relativePath), readOptions),
|
|
219
|
-
readBytes: async (relativePath, readOptions) => await (await openRoot()).readBytes(assertRelativePath(relativePath), readOptions),
|
|
215
|
+
open: async (relativePath, readOptions) => await (await openRoot()).open(literalStoreRootPath(assertRelativePath(relativePath)), readOptions),
|
|
216
|
+
read: async (relativePath, readOptions) => await (await openRoot()).read(literalStoreRootPath(assertRelativePath(relativePath)), readOptions),
|
|
217
|
+
readBytes: async (relativePath, readOptions) => await (await openRoot()).readBytes(literalStoreRootPath(assertRelativePath(relativePath)), readOptions),
|
|
220
218
|
readText: async (relativePath, readOptions) => {
|
|
221
219
|
const { encoding = "utf8", ...options } = readOptions ?? {};
|
|
222
|
-
return (await (await openRoot()).read(assertRelativePath(relativePath), options)).buffer
|
|
220
|
+
return (await (await openRoot()).read(literalStoreRootPath(assertRelativePath(relativePath)), options)).buffer
|
|
223
221
|
.toString(encoding);
|
|
224
222
|
},
|
|
225
223
|
readTextIfExists: async (relativePath, readOptions) => {
|
|
226
224
|
try {
|
|
227
|
-
return await (await openRoot()).readText(assertRelativePath(relativePath), readOptions);
|
|
225
|
+
return await (await openRoot()).readText(literalStoreRootPath(assertRelativePath(relativePath)), readOptions);
|
|
228
226
|
}
|
|
229
227
|
catch (error) {
|
|
230
228
|
if (isNotFound(error)) {
|
|
@@ -235,12 +233,12 @@ export function fileStore(options) {
|
|
|
235
233
|
},
|
|
236
234
|
readJson: async (relativePath, readOptions) => {
|
|
237
235
|
const { encoding = "utf8", ...options } = readOptions ?? {};
|
|
238
|
-
return JSON.parse((await (await openRoot()).read(assertRelativePath(relativePath), options)).buffer
|
|
236
|
+
return JSON.parse((await (await openRoot()).read(literalStoreRootPath(assertRelativePath(relativePath)), options)).buffer
|
|
239
237
|
.toString(encoding));
|
|
240
238
|
},
|
|
241
239
|
readJsonIfExists: async (relativePath, readOptions) => {
|
|
242
240
|
try {
|
|
243
|
-
return await (await openRoot()).readJson(assertRelativePath(relativePath), readOptions);
|
|
241
|
+
return await (await openRoot()).readJson(literalStoreRootPath(assertRelativePath(relativePath)), readOptions);
|
|
244
242
|
}
|
|
245
243
|
catch (error) {
|
|
246
244
|
if (isNotFound(error)) {
|
|
@@ -250,9 +248,9 @@ export function fileStore(options) {
|
|
|
250
248
|
}
|
|
251
249
|
},
|
|
252
250
|
remove: async (relativePath) => {
|
|
253
|
-
await (await openRoot()).remove(assertRelativePath(relativePath));
|
|
251
|
+
await (await openRoot()).remove(literalStoreRootPath(assertRelativePath(relativePath)));
|
|
254
252
|
},
|
|
255
|
-
exists: async (relativePath) => await (await openRoot()).exists(assertRelativePath(relativePath)),
|
|
253
|
+
exists: async (relativePath) => await (await openRoot()).exists(literalStoreRootPath(assertRelativePath(relativePath))),
|
|
256
254
|
writeText: async (relativePath, data, writeOptions) => await write(relativePath, data, writeOptions),
|
|
257
255
|
writeJson: async (relativePath, data, writeOptions) => {
|
|
258
256
|
const trailingNewline = writeOptions?.trailingNewline;
|
|
@@ -269,7 +267,7 @@ export function fileStore(options) {
|
|
|
269
267
|
} : {}),
|
|
270
268
|
readIfExists: async () => {
|
|
271
269
|
try {
|
|
272
|
-
return await (await openRoot()).readJson(
|
|
270
|
+
return await (await openRoot()).readJson(literalStoreRootPath(relativePath));
|
|
273
271
|
}
|
|
274
272
|
catch (error) {
|
|
275
273
|
if (isNotFound(error)) {
|
|
@@ -278,7 +276,7 @@ export function fileStore(options) {
|
|
|
278
276
|
throw error;
|
|
279
277
|
}
|
|
280
278
|
},
|
|
281
|
-
readRequired: async () => await (await openRoot()).readJson(
|
|
279
|
+
readRequired: async () => await (await openRoot()).readJson(literalStoreRootPath(relativePath)),
|
|
282
280
|
write: async (value, options) => {
|
|
283
281
|
const json = stringifyJsonDocument(value, null, 2);
|
|
284
282
|
await write(relativePath, options?.trailingNewline === false ? json : `${json}\n`, { durable: options?.durable });
|
|
@@ -36,15 +36,6 @@ export const GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON = [
|
|
|
36
36
|
" is_linux = sys.platform.startswith('linux')",
|
|
37
37
|
" if is_linux:",
|
|
38
38
|
" rename_fn = getattr(libc, 'renameat2', None)",
|
|
39
|
-
" if rename_fn is None:",
|
|
40
|
-
" os.link(",
|
|
41
|
-
" src_basename,",
|
|
42
|
-
" dst_basename,",
|
|
43
|
-
" src_dir_fd=src_parent_fd,",
|
|
44
|
-
" dst_dir_fd=dst_parent_fd,",
|
|
45
|
-
" follow_symlinks=False,",
|
|
46
|
-
" )",
|
|
47
|
-
" return",
|
|
48
39
|
" flags = 1 # RENAME_NOREPLACE",
|
|
49
40
|
" elif sys.platform == 'darwin':",
|
|
50
41
|
" rename_fn = getattr(libc, 'renameatx_np', None)",
|
|
@@ -52,32 +43,33 @@ export const GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON = [
|
|
|
52
43
|
" else:",
|
|
53
44
|
" rename_fn = None",
|
|
54
45
|
" flags = 0",
|
|
55
|
-
" if rename_fn is None:",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
46
|
+
" if rename_fn is not None:",
|
|
47
|
+
" rename_fn.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint]",
|
|
48
|
+
" rename_fn.restype = ctypes.c_int",
|
|
49
|
+
" result = rename_fn(",
|
|
50
|
+
" src_parent_fd,",
|
|
51
|
+
" os.fsencode(src_basename),",
|
|
52
|
+
" dst_parent_fd,",
|
|
53
|
+
" os.fsencode(dst_basename),",
|
|
54
|
+
" flags,",
|
|
55
|
+
" )",
|
|
56
|
+
" if result == 0:",
|
|
57
|
+
" return",
|
|
67
58
|
" error_code = ctypes.get_errno()",
|
|
68
59
|
" unsupported_codes = {errno.ENOSYS, errno.EINVAL, errno.ENOTSUP}",
|
|
69
60
|
" if hasattr(errno, 'EOPNOTSUPP'):",
|
|
70
61
|
" unsupported_codes.add(errno.EOPNOTSUPP)",
|
|
71
|
-
" if is_linux
|
|
72
|
-
" os.
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
62
|
+
" if not is_linux or error_code not in unsupported_codes:",
|
|
63
|
+
" raise OSError(error_code, os.strerror(error_code), dst_basename)",
|
|
64
|
+
" elif not is_linux:",
|
|
65
|
+
" raise OSError(errno.ENOSYS, 'atomic no-replace rename is unavailable')",
|
|
66
|
+
" os.link(",
|
|
67
|
+
" src_basename,",
|
|
68
|
+
" dst_basename,",
|
|
69
|
+
" src_dir_fd=src_parent_fd,",
|
|
70
|
+
" dst_dir_fd=dst_parent_fd,",
|
|
71
|
+
" follow_symlinks=False,",
|
|
72
|
+
" )",
|
|
81
73
|
].join("\n");
|
|
82
74
|
export const GUEST_FILESYSTEM_CREATE_EXCLUSIVE_PYTHON = [
|
|
83
75
|
"def create_exclusive(parent_fd, basename, stdin_buffer):",
|
package/dist/guest.js
CHANGED
|
@@ -270,13 +270,15 @@ export const GUEST_FILESYSTEM_PYTHON = [
|
|
|
270
270
|
"def copy_entry(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
|
|
271
271
|
" src_stat = os.lstat(src_basename, dir_fd=src_parent_fd)",
|
|
272
272
|
" if stat.S_ISDIR(src_stat.st_mode) and not stat.S_ISLNK(src_stat.st_mode):",
|
|
273
|
-
" os.mkdir(dst_basename, stat.S_IMODE(src_stat.st_mode) or 0o755, dir_fd=dst_parent_fd)",
|
|
274
273
|
" copied_children = []",
|
|
275
274
|
" src_dir_fd = None",
|
|
276
275
|
" dst_dir_fd = None",
|
|
276
|
+
" destination_created = False",
|
|
277
277
|
" try:",
|
|
278
278
|
" src_dir_fd = open_dir(src_basename, dir_fd=src_parent_fd)",
|
|
279
279
|
" src_stat = os.fstat(src_dir_fd)",
|
|
280
|
+
" os.mkdir(dst_basename, stat.S_IMODE(src_stat.st_mode), dir_fd=dst_parent_fd)",
|
|
281
|
+
" destination_created = True",
|
|
280
282
|
" dst_dir_fd = open_dir(dst_basename, dir_fd=dst_parent_fd)",
|
|
281
283
|
" for child in os.listdir(src_dir_fd):",
|
|
282
284
|
" copied_children.append((child, copy_entry(src_dir_fd, child, dst_dir_fd, child)))",
|
|
@@ -284,10 +286,11 @@ export const GUEST_FILESYSTEM_PYTHON = [
|
|
|
284
286
|
" if dst_dir_fd is not None:",
|
|
285
287
|
" os.close(dst_dir_fd)",
|
|
286
288
|
" dst_dir_fd = None",
|
|
287
|
-
"
|
|
288
|
-
"
|
|
289
|
-
"
|
|
290
|
-
"
|
|
289
|
+
" if destination_created:",
|
|
290
|
+
" try:",
|
|
291
|
+
" remove_tree(dst_parent_fd, dst_basename)",
|
|
292
|
+
" except FileNotFoundError:",
|
|
293
|
+
" pass",
|
|
291
294
|
" raise",
|
|
292
295
|
" finally:",
|
|
293
296
|
" if src_dir_fd is not None:",
|
|
@@ -328,30 +331,36 @@ export const GUEST_FILESYSTEM_PYTHON = [
|
|
|
328
331
|
" raise",
|
|
329
332
|
" src_stat = os.lstat(src_basename, dir_fd=src_parent_fd)",
|
|
330
333
|
" if stat.S_ISDIR(src_stat.st_mode) and not stat.S_ISLNK(src_stat.st_mode):",
|
|
331
|
-
" temp_dir_name =
|
|
334
|
+
" temp_dir_name = None",
|
|
332
335
|
" copied_children = []",
|
|
333
336
|
" temp_dir_fd = None",
|
|
334
337
|
" src_dir_fd = None",
|
|
335
338
|
" try:",
|
|
336
|
-
" temp_dir_fd = open_dir(temp_dir_name, dir_fd=dst_parent_fd)",
|
|
337
339
|
" src_dir_fd = open_dir(src_basename, dir_fd=src_parent_fd)",
|
|
338
340
|
" src_stat = os.fstat(src_dir_fd)",
|
|
341
|
+
" source_mode = stat.S_IMODE(src_stat.st_mode)",
|
|
342
|
+
" temp_dir_name = create_temp_dir(dst_parent_fd, source_mode or 0o700)",
|
|
343
|
+
" temp_dir_fd = open_dir(temp_dir_name, dir_fd=dst_parent_fd)",
|
|
339
344
|
" for child in os.listdir(src_dir_fd):",
|
|
340
345
|
" copied_children.append((child, copy_entry(src_dir_fd, child, temp_dir_fd, child)))",
|
|
341
346
|
" os.close(src_dir_fd)",
|
|
342
347
|
" src_dir_fd = None",
|
|
348
|
+
" os.rename(temp_dir_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
|
|
349
|
+
" temp_dir_name = None",
|
|
350
|
+
" if source_mode == 0:",
|
|
351
|
+
" os.fchmod(temp_dir_fd, 0)",
|
|
343
352
|
" os.close(temp_dir_fd)",
|
|
344
353
|
" temp_dir_fd = None",
|
|
345
|
-
" os.rename(temp_dir_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
|
|
346
354
|
" except Exception:",
|
|
347
355
|
" if src_dir_fd is not None:",
|
|
348
356
|
" os.close(src_dir_fd)",
|
|
349
357
|
" if temp_dir_fd is not None:",
|
|
350
358
|
" os.close(temp_dir_fd)",
|
|
351
|
-
"
|
|
352
|
-
"
|
|
353
|
-
"
|
|
354
|
-
"
|
|
359
|
+
" if temp_dir_name is not None:",
|
|
360
|
+
" try:",
|
|
361
|
+
" remove_tree(dst_parent_fd, temp_dir_name)",
|
|
362
|
+
" except FileNotFoundError:",
|
|
363
|
+
" pass",
|
|
355
364
|
" raise",
|
|
356
365
|
" remove_copied_entry(src_parent_fd, src_basename, ('dir', entry_identity(src_stat), copied_children))",
|
|
357
366
|
" os.fsync(dst_parent_fd)",
|
|
@@ -4,11 +4,7 @@ export type DurableQueueEntryPathsLike = {
|
|
|
4
4
|
deliveredPath: string;
|
|
5
5
|
processingPath?: string;
|
|
6
6
|
};
|
|
7
|
-
export type ValidatedDurableQueueEntryPaths = Readonly<
|
|
8
|
-
jsonPath: string;
|
|
9
|
-
deliveredPath: string;
|
|
10
|
-
processingPath: string;
|
|
11
|
-
}>;
|
|
7
|
+
export type ValidatedDurableQueueEntryPaths = Readonly<Required<DurableQueueEntryPathsLike>>;
|
|
12
8
|
export declare function validateDurableQueueEntryPaths(paths: DurableQueueEntryPathsLike): ValidatedDurableQueueEntryPaths;
|
|
13
9
|
export declare function claimDurableQueueEntry(paths: DurableQueueEntryPathsLike, options?: {
|
|
14
10
|
skipUnowned?: boolean;
|
|
@@ -8,7 +8,6 @@ import { stringifyJsonDocument } from "./json-stringify.js";
|
|
|
8
8
|
import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
|
|
9
9
|
import { assertSafePathSegment } from "./safe-path-segment.js";
|
|
10
10
|
import { admitStandalonePublicationPath, assertNoWindowsPathAlias, hasWindowsPathAlias, } from "./windows-path-alias.js";
|
|
11
|
-
export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
|
|
12
11
|
function assertSafeQueueEntryId(id) {
|
|
13
12
|
assertSafePathSegment(id, { label: "queue entry id" });
|
|
14
13
|
}
|
|
@@ -146,8 +145,7 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
146
145
|
await unlinkStaleTmpBestEffort(path.join(queueDir, file), now, options.cleanupTmpMaxAgeMs);
|
|
147
146
|
}
|
|
148
147
|
}
|
|
149
|
-
const ids =
|
|
150
|
-
const seenIds = new Set();
|
|
148
|
+
const ids = new Set();
|
|
151
149
|
for (const file of files) {
|
|
152
150
|
const suffix = file.endsWith(".processing")
|
|
153
151
|
? ".processing"
|
|
@@ -163,10 +161,7 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
163
161
|
catch {
|
|
164
162
|
continue;
|
|
165
163
|
}
|
|
166
|
-
|
|
167
|
-
continue;
|
|
168
|
-
seenIds.add(id);
|
|
169
|
-
ids.push(id);
|
|
164
|
+
ids.add(id);
|
|
170
165
|
}
|
|
171
166
|
const entries = [];
|
|
172
167
|
for (const id of ids) {
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { BigIntStats, Stats } from "node:fs";
|
|
2
2
|
import type { FileHandle } from "node:fs/promises";
|
|
3
|
-
import type {
|
|
4
|
-
import type { SymlinkPolicy } from "./root-symlink-policy.js";
|
|
3
|
+
import type { RootReadOptions } from "./root-options.js";
|
|
5
4
|
type OwnedLocalFile = {
|
|
6
5
|
handle: FileHandle;
|
|
7
6
|
stat: Stats;
|
|
8
7
|
identity: BigIntStats;
|
|
9
8
|
preOpenStat: BigIntStats | undefined;
|
|
10
9
|
};
|
|
11
|
-
export declare function openLocalFileDescriptor(filePath: string, options?: {
|
|
12
|
-
hardlinks?: HardlinkPolicy;
|
|
13
|
-
symlinks?: SymlinkPolicy;
|
|
10
|
+
export declare function openLocalFileDescriptor(filePath: string, options?: Pick<RootReadOptions, "hardlinks" | "symlinks"> & {
|
|
14
11
|
readWrite?: true;
|
|
15
12
|
}): Promise<OwnedLocalFile>;
|
|
16
13
|
export {};
|
package/dist/local-roots.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ReadResult, type RootReadOptions } from "./root.js";
|
|
2
2
|
export type LocalRootsPathResult = {
|
|
3
3
|
path: string;
|
|
4
4
|
root: string;
|
|
@@ -15,11 +15,6 @@ export type ResolveLocalPathFromRootsSyncOptions = LocalRootsInputOptions & {
|
|
|
15
15
|
allowMissing?: boolean;
|
|
16
16
|
requireFile?: boolean;
|
|
17
17
|
};
|
|
18
|
-
export type ReadLocalFileFromRootsOptions = LocalRootsInputOptions &
|
|
19
|
-
hardlinks?: HardlinkPolicy;
|
|
20
|
-
maxBytes?: number;
|
|
21
|
-
nonBlockingRead?: boolean;
|
|
22
|
-
symlinks?: SymlinkPolicy;
|
|
23
|
-
};
|
|
18
|
+
export type ReadLocalFileFromRootsOptions = LocalRootsInputOptions & RootReadOptions;
|
|
24
19
|
export declare function resolveLocalPathFromRootsSync(options: ResolveLocalPathFromRootsSyncOptions): LocalRootsPathResult | null;
|
|
25
20
|
export declare function readLocalFileFromRoots(options: ReadLocalFileFromRootsOptions): Promise<LocalRootsReadResult | null>;
|
package/dist/native-binding.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TarMeterLimits } from "./archive-limits.js";
|
|
2
2
|
import type { ArchiveMemberKind } from "./archive-plan.js";
|
|
3
3
|
import type { CopyCloneMode } from "./copy-policy.js";
|
|
4
|
+
import type { WindowsAceFlags } from "./owner-dacl.js";
|
|
4
5
|
export interface NativeFileHash {
|
|
5
6
|
bytes: number;
|
|
6
7
|
digest: string;
|
|
@@ -58,16 +59,7 @@ export interface NativeWindowsAccessControlEntry {
|
|
|
58
59
|
sid: string;
|
|
59
60
|
mask: number;
|
|
60
61
|
aceType: string;
|
|
61
|
-
flags:
|
|
62
|
-
raw: number;
|
|
63
|
-
objectInherit: boolean;
|
|
64
|
-
containerInherit: boolean;
|
|
65
|
-
noPropagateInherit: boolean;
|
|
66
|
-
inheritOnly: boolean;
|
|
67
|
-
inherited: boolean;
|
|
68
|
-
successfulAccess: boolean;
|
|
69
|
-
failedAccess: boolean;
|
|
70
|
-
};
|
|
62
|
+
flags: WindowsAceFlags;
|
|
71
63
|
}
|
|
72
64
|
export interface NativeWindowsSecurityFacts {
|
|
73
65
|
ownerSid: string;
|
|
@@ -96,6 +88,12 @@ export interface NativeWindowsDirectoryReceipt {
|
|
|
96
88
|
export interface NativeDarwinAclFacts {
|
|
97
89
|
state: "absent" | "empty" | "present";
|
|
98
90
|
}
|
|
91
|
+
type NativeTwoPathArgs = [
|
|
92
|
+
sourceRootFd: number,
|
|
93
|
+
sourceRelPath: string,
|
|
94
|
+
targetRootFd: number,
|
|
95
|
+
targetRelPath: string
|
|
96
|
+
];
|
|
99
97
|
export interface NativeBinding {
|
|
100
98
|
/** Internal: consumes only a descriptor returned by this binding. */
|
|
101
99
|
closeOwnedFd(fd: number): void;
|
|
@@ -141,7 +139,7 @@ export interface NativeBinding {
|
|
|
141
139
|
extractArchiveNative(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
|
|
142
140
|
fstatIdentity(fd: number): NativeFileIdentity;
|
|
143
141
|
inspectArchiveNative(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
|
|
144
|
-
linkBeneath(
|
|
142
|
+
linkBeneath(...args: NativeTwoPathArgs): void;
|
|
145
143
|
/** Direct-child mkdir; true is receipt provenance only, never cleanup ownership. */
|
|
146
144
|
mkdirChildBeneath?(parentFd: number, basename: string, mode: number): boolean;
|
|
147
145
|
mkdirBeneath(rootFd: number, relPath: string, mode: number): void;
|
|
@@ -153,10 +151,11 @@ export interface NativeBinding {
|
|
|
153
151
|
ownedTreeRemovalAvailable?(parentFd: number): boolean;
|
|
154
152
|
removeOwnedTree?(parentFd: number, basename: string, directoryFd: number): Promise<NativeOwnedTreeRemovalResult>;
|
|
155
153
|
removeOwnedTreeSync?(parentFd: number, basename: string, directoryFd: number): NativeOwnedTreeRemovalResult;
|
|
156
|
-
renameNoReplace(
|
|
154
|
+
renameNoReplace(...args: NativeTwoPathArgs): void;
|
|
157
155
|
/** Identity-fenced retained-directory rename capability. */
|
|
158
156
|
renameNoReplaceWithIdentity?(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string, expectedSourceDev: bigint, expectedSourceIno: bigint): void;
|
|
159
|
-
renameReplace(
|
|
157
|
+
renameReplace(...args: NativeTwoPathArgs): void;
|
|
160
158
|
sha256File(fd: number, maxBytes?: number, signal?: AbortSignal): Promise<NativeFileHash>;
|
|
161
159
|
}
|
|
162
160
|
export declare function captureNativeFdClose(binding: NativeBinding): (fd: number) => void;
|
|
161
|
+
export {};
|
|
@@ -7,6 +7,12 @@ export type PermissionCommandFailure = {
|
|
|
7
7
|
signal: string | null;
|
|
8
8
|
stderr: string;
|
|
9
9
|
};
|
|
10
|
+
export type PermissionFailureFields = {
|
|
11
|
+
error?: string;
|
|
12
|
+
errorDetail?: PermissionCommandFailure;
|
|
13
|
+
/** Original inspection failure, retained separately from serializable diagnostics. */
|
|
14
|
+
errorCause?: unknown;
|
|
15
|
+
};
|
|
10
16
|
export declare function formatPermissionErrorDetail(value: string): string;
|
|
11
17
|
/** Formats only caught permission-query failures without invoking user code. */
|
|
12
18
|
export declare function formatCaughtPermissionFailure(error: unknown): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { formatOctal, formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, safeStat, type PermissionCheck, type PermissionCheckOptions, type
|
|
1
|
+
export { formatOctal, formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, safeStat, type PermissionCheck, type PermissionCheckOptions, type SafeStatResult, } from "./permissions.js";
|
|
2
|
+
export type { PermissionCommandFailure } from "./permission-exec.js";
|
|
2
3
|
export { createPrivateDirectory, type CreatePrivateDirectoryOptions, } from "./private-directory.js";
|
|
3
4
|
export { readOwnerAndDacl, type OwnerAndDaclResult, type WindowsAccessControlEntry, type WindowsAceFlags, } from "./owner-dacl.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type PermissionFailureFields } from "./permission-exec.js";
|
|
2
2
|
import { type PermissionCheck, type PermissionCheckOptions } from "./permissions.js";
|
|
3
3
|
export type PermissionExec = (command: string, args: string[]) => Promise<{
|
|
4
4
|
stdout: string;
|
|
@@ -13,16 +13,13 @@ export type WindowsAclEntry = {
|
|
|
13
13
|
canRead: boolean;
|
|
14
14
|
canWrite: boolean;
|
|
15
15
|
};
|
|
16
|
-
export type WindowsAclSummary = {
|
|
16
|
+
export type WindowsAclSummary = Omit<PermissionFailureFields & {
|
|
17
17
|
ok: boolean;
|
|
18
18
|
entries: WindowsAclEntry[];
|
|
19
19
|
untrustedWorld: WindowsAclEntry[];
|
|
20
20
|
untrustedGroup: WindowsAclEntry[];
|
|
21
21
|
trusted: WindowsAclEntry[];
|
|
22
|
-
|
|
23
|
-
errorDetail?: PermissionCommandFailure;
|
|
24
|
-
errorCause?: unknown;
|
|
25
|
-
};
|
|
22
|
+
}, never>;
|
|
26
23
|
export type WindowsUserInfoProvider = () => {
|
|
27
24
|
username?: string | null;
|
|
28
25
|
};
|
package/dist/permissions.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PermissionFailureFields } from "./permission-exec.js";
|
|
2
2
|
import { type PermissionExec } from "./permissions-windows.js";
|
|
3
|
-
export type
|
|
4
|
-
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
|
|
5
|
-
export type { IcaclsResetCommandOptions, PermissionExec, WindowsAclEntry, WindowsAclSummary, WindowsUserInfoProvider, } from "./permissions-windows.js";
|
|
6
|
-
export type PermissionCheck = {
|
|
3
|
+
export type PermissionCheck = Omit<PermissionFailureFields & {
|
|
7
4
|
ok: boolean;
|
|
8
5
|
isSymlink: boolean;
|
|
9
6
|
isDir: boolean;
|
|
@@ -21,11 +18,7 @@ export type PermissionCheck = {
|
|
|
21
18
|
/** Owner-query failure detail when Windows ownership could not be verified. */
|
|
22
19
|
ownerError?: string;
|
|
23
20
|
aclSummary?: string;
|
|
24
|
-
|
|
25
|
-
errorDetail?: PermissionCommandFailure;
|
|
26
|
-
/** Original inspection failure, retained separately from serializable diagnostics. */
|
|
27
|
-
errorCause?: unknown;
|
|
28
|
-
};
|
|
21
|
+
}, never>;
|
|
29
22
|
export type PermissionCheckOptions = {
|
|
30
23
|
platform?: NodeJS.Platform;
|
|
31
24
|
env?: NodeJS.ProcessEnv;
|
package/dist/permissions.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import { formatIcaclsResetCommand, inspectWindowsPermissions, } from "./permissions-windows.js";
|
|
3
3
|
import { hasWindowsPathAlias, pathForWindowsFilesystem, } from "./windows-path-alias.js";
|
|
4
|
-
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
|
|
5
4
|
function failedSafeStat(error) {
|
|
6
5
|
return {
|
|
7
6
|
ok: false,
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PinnedMutationPolicySnapshot } from "./pinned-mutation-admission.js";
|
|
2
2
|
import type { RootBoundaryIdentity } from "./root-boundary.js";
|
|
3
|
-
import type { MutationSymlinkPolicy } from "./root-symlink-policy.js";
|
|
4
3
|
export type ExactRootIdentity = Readonly<{
|
|
5
4
|
dev: bigint;
|
|
6
5
|
ino: bigint;
|
|
7
6
|
}>;
|
|
8
|
-
type SharedMutationPolicy = Readonly<{
|
|
9
|
-
denyMutations?: DenyMutationPolicy;
|
|
10
|
-
mutationSymlinks?: MutationSymlinkPolicy;
|
|
11
|
-
}>;
|
|
12
7
|
export declare function ordinaryWindowsSegments(relativePath: string): boolean;
|
|
13
8
|
export declare function ordinarySharedAbsoluteInsideRoot(rootReal: string, candidatePath: string, rootIdentity: ExactRootIdentity): boolean;
|
|
14
9
|
export declare function simpleSharedRoute(params: {
|
|
@@ -16,9 +11,8 @@ export declare function simpleSharedRoute(params: {
|
|
|
16
11
|
rootIdentity?: RootBoundaryIdentity;
|
|
17
12
|
originalPath?: string;
|
|
18
13
|
selectedTarget: string;
|
|
19
|
-
policy:
|
|
14
|
+
policy: PinnedMutationPolicySnapshot;
|
|
20
15
|
}): {
|
|
21
16
|
route: string;
|
|
22
17
|
rootIdentity: ExactRootIdentity;
|
|
23
18
|
} | undefined;
|
|
24
|
-
export {};
|
package/dist/pinned-open.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type PinnedOpenSyncFinalAdmission = (params: {
|
|
|
18
18
|
descriptorIdentity: fs.BigIntStats;
|
|
19
19
|
}) => string;
|
|
20
20
|
export type PinnedOpenSyncFs = Pick<typeof fs, "constants" | "lstatSync" | "realpathSync" | "openSync" | "fstatSync" | "closeSync">;
|
|
21
|
+
export declare function isExpectedPathError(error: unknown): boolean;
|
|
21
22
|
export declare function openPinnedFileSync(params: {
|
|
22
23
|
filePath: string;
|
|
23
24
|
resolvedPath?: string;
|
package/dist/pinned-open.js
CHANGED
|
@@ -5,7 +5,7 @@ import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
|
5
5
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
6
6
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
7
7
|
import { realpathSync } from "./realpath.js";
|
|
8
|
-
function isExpectedPathError(error) {
|
|
8
|
+
export function isExpectedPathError(error) {
|
|
9
9
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
10
10
|
return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
|
|
11
11
|
}
|
|
@@ -6,7 +6,7 @@ import { assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryId
|
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { classifyNativeRenameFailure, NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH, } from "./native-rename-outcome.js";
|
|
8
8
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
9
|
-
import { RetainedDirectoryReplacement } from "./retained-directory-replacement.js";
|
|
9
|
+
import { copyOperationalCode, RetainedDirectoryReplacement } from "./retained-directory-replacement.js";
|
|
10
10
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
11
11
|
import { admitStandalonePublicationPath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
12
12
|
import { serializePathWrite } from "./write-queue.js";
|
|
@@ -17,30 +17,18 @@ function assertGuardsCurrent(guards) {
|
|
|
17
17
|
for (const guard of guards)
|
|
18
18
|
assertSyncDirectoryGuard(guard);
|
|
19
19
|
}
|
|
20
|
-
function
|
|
21
|
-
const code = source?.code;
|
|
22
|
-
if (typeof code === "string") {
|
|
23
|
-
Object.defineProperty(target, "code", { configurable: true, value: code });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function replacementFailure(error, message, details) {
|
|
20
|
+
function replacementFailure(error, message, details, cause = error) {
|
|
27
21
|
if (error instanceof FsSafeError) {
|
|
28
|
-
return new FsSafeError(error.code, message, { cause
|
|
22
|
+
return new FsSafeError(error.code, message, { cause, details });
|
|
29
23
|
}
|
|
30
|
-
const failure = new Error(message, { cause
|
|
24
|
+
const failure = new Error(message, { cause });
|
|
31
25
|
copyOperationalCode(failure, error);
|
|
32
26
|
Object.defineProperty(failure, "details", { configurable: true, value: details });
|
|
33
27
|
return failure;
|
|
34
28
|
}
|
|
35
29
|
function replacementAggregate(error, recoveryError, message, details) {
|
|
36
30
|
const combined = new AggregateError([error, recoveryError], "directory replacement operation and recovery both failed");
|
|
37
|
-
|
|
38
|
-
return new FsSafeError(error.code, message, { cause: combined, details });
|
|
39
|
-
}
|
|
40
|
-
const failure = new Error(message, { cause: combined });
|
|
41
|
-
copyOperationalCode(failure, error);
|
|
42
|
-
Object.defineProperty(failure, "details", { configurable: true, value: details });
|
|
43
|
-
return failure;
|
|
31
|
+
return replacementFailure(error, message, details, combined);
|
|
44
32
|
}
|
|
45
33
|
function isMissingDirectoryEntry(error) {
|
|
46
34
|
return error?.code === "ENOENT";
|
|
@@ -24,15 +24,8 @@ export declare function copyFallbackReplace(params: {
|
|
|
24
24
|
expectedSourceIdentity?: BigIntStats;
|
|
25
25
|
sync: boolean;
|
|
26
26
|
}): Promise<void>;
|
|
27
|
-
export declare function copyFallbackReplaceSync(params: {
|
|
27
|
+
export declare function copyFallbackReplaceSync(params: Omit<Parameters<typeof copyFallbackReplace>[0], "fsModule"> & {
|
|
28
28
|
fsModule: SyncFallbackFs;
|
|
29
|
-
src: string;
|
|
30
|
-
dest: string;
|
|
31
|
-
destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
|
|
32
|
-
restore: ReplaceFileCopyFallbackRestorePolicy;
|
|
33
|
-
maxRestoreBytes?: number;
|
|
34
|
-
expectedSourceIdentity?: BigIntStats;
|
|
35
29
|
fchmodSync?: (fd: number, mode: number) => void;
|
|
36
|
-
sync: boolean;
|
|
37
30
|
}): void;
|
|
38
31
|
export {};
|