nawabari 0.9.2 → 0.10.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/README.md +142 -803
- package/dist/cli-command-registry.js +46 -6
- package/dist/cli-command-registry.js.map +1 -1
- package/dist/cli.d.ts +5 -3
- package/dist/cli.js +157 -34
- package/dist/cli.js.map +1 -1
- package/dist/contract.js +11 -3
- package/dist/contract.js.map +1 -1
- package/dist/domain/compatibility-runtime-projection.d.ts +24 -0
- package/dist/domain/compatibility-runtime-projection.js +197 -0
- package/dist/domain/compatibility-runtime-projection.js.map +1 -0
- package/dist/domain/doctor.d.ts +2 -2
- package/dist/domain/doctor.js +72 -51
- package/dist/domain/doctor.js.map +1 -1
- package/dist/domain/errors.d.ts +1 -1
- package/dist/domain/errors.js +5 -1
- package/dist/domain/errors.js.map +1 -1
- package/dist/domain/fhs-development-runtime.d.ts +90 -0
- package/dist/domain/fhs-development-runtime.js +385 -0
- package/dist/domain/fhs-development-runtime.js.map +1 -0
- package/dist/domain/fhs-runtime.d.ts +36 -0
- package/dist/domain/fhs-runtime.js +776 -0
- package/dist/domain/fhs-runtime.js.map +1 -0
- package/dist/domain/landlock.d.ts +11 -4
- package/dist/domain/landlock.js +44 -21
- package/dist/domain/landlock.js.map +1 -1
- package/dist/domain/nix-runtime-closure.d.ts +70 -0
- package/dist/domain/nix-runtime-closure.js +449 -0
- package/dist/domain/nix-runtime-closure.js.map +1 -0
- package/dist/domain/pnpm-middleware-backend-materialization.d.ts +117 -0
- package/dist/domain/pnpm-middleware-backend-materialization.js +311 -0
- package/dist/domain/pnpm-middleware-backend-materialization.js.map +1 -0
- package/dist/domain/runtime-executable-projection.d.ts +33 -0
- package/dist/domain/runtime-executable-projection.js +204 -0
- package/dist/domain/runtime-executable-projection.js.map +1 -0
- package/dist/domain/runtime-file-identity.d.ts +37 -0
- package/dist/domain/runtime-file-identity.js +114 -0
- package/dist/domain/runtime-file-identity.js.map +1 -0
- package/dist/domain/runtime-profile.d.ts +66 -0
- package/dist/domain/runtime-profile.js +356 -0
- package/dist/domain/runtime-profile.js.map +1 -0
- package/dist/domain/runtime-projection.d.ts +101 -0
- package/dist/domain/runtime-projection.js +316 -0
- package/dist/domain/runtime-projection.js.map +1 -0
- package/dist/domain/runtime-provider-pnpm-middleware.d.ts +41 -0
- package/dist/domain/runtime-provider-pnpm-middleware.js +316 -0
- package/dist/domain/runtime-provider-pnpm-middleware.js.map +1 -0
- package/dist/domain/runtime-provider-tgrep.d.ts +60 -0
- package/dist/domain/runtime-provider-tgrep.js +537 -0
- package/dist/domain/runtime-provider-tgrep.js.map +1 -0
- package/dist/domain/runtime-resolution.d.ts +57 -0
- package/dist/domain/runtime-resolution.js +242 -0
- package/dist/domain/runtime-resolution.js.map +1 -0
- package/dist/domain/sandbox-launcher.d.ts +7 -0
- package/dist/domain/sandbox-launcher.js +380 -56
- package/dist/domain/sandbox-launcher.js.map +1 -1
- package/dist/domain/sandbox.d.ts +59 -4
- package/dist/domain/sandbox.js +100 -5
- package/dist/domain/sandbox.js.map +1 -1
- package/dist/domain/session-backend.js +20 -1
- package/dist/domain/session-backend.js.map +1 -1
- package/dist/domain/session.d.ts +3 -2
- package/dist/domain/session.js.map +1 -1
- package/dist/domain/tgrep-runtime-materialization.d.ts +61 -0
- package/dist/domain/tgrep-runtime-materialization.js +172 -0
- package/dist/domain/tgrep-runtime-materialization.js.map +1 -0
- package/dist/operation-authorization.d.ts +10 -0
- package/dist/operation-authorization.js +12 -1
- package/dist/operation-authorization.js.map +1 -1
- package/dist/presentation.js +6 -3
- package/dist/presentation.js.map +1 -1
- package/dist/session-registry.d.ts +11 -3
- package/dist/session-registry.js +61 -45
- package/dist/session-registry.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { posix } from "node:path";
|
|
3
|
+
import { DomainError, failure, success } from "./errors.js";
|
|
4
|
+
import { inspectRuntimeFile, readRuntimeFile } from "./runtime-file-identity.js";
|
|
5
|
+
import { RUNTIME_PROFILE_CONTRACT_ID, RUNTIME_PROFILE_SCHEMA_VERSION, } from "./runtime-profile.js";
|
|
6
|
+
import { runtimeMaterializationMissingError, STRICT_RUNTIME_POLICY, validateRuntimePolicy, validateSessionRuntimeProjection, } from "./runtime-projection.js";
|
|
7
|
+
/** Versioned identity for bounded standalone/FHS runtime materialization. */
|
|
8
|
+
export const FHS_RUNTIME_MATERIALIZATION_CONTRACT_ID = "nawabari.fhs-runtime-materialization.v1";
|
|
9
|
+
export const FHS_RUNTIME_MATERIALIZATION_SCHEMA_VERSION = 1;
|
|
10
|
+
/** The only host roots accepted as FHS materialization destinations. */
|
|
11
|
+
export const FHS_RUNTIME_ROOTS = Object.freeze(["/usr", "/bin", "/lib", "/lib64"]);
|
|
12
|
+
/** Fixed, distro-independent lookup order for the common FHS library layouts. */
|
|
13
|
+
export const FHS_RUNTIME_LIBRARY_SEARCH_PATHS = Object.freeze([
|
|
14
|
+
"/lib",
|
|
15
|
+
"/lib64",
|
|
16
|
+
"/usr/lib",
|
|
17
|
+
"/usr/lib64",
|
|
18
|
+
"/lib/aarch64-linux-gnu",
|
|
19
|
+
"/lib/arm-linux-gnueabi",
|
|
20
|
+
"/lib/arm-linux-gnueabihf",
|
|
21
|
+
"/lib/i386-linux-gnu",
|
|
22
|
+
"/lib/powerpc64le-linux-gnu",
|
|
23
|
+
"/lib/riscv64-linux-gnu",
|
|
24
|
+
"/lib/s390x-linux-gnu",
|
|
25
|
+
"/lib/x86_64-linux-gnu",
|
|
26
|
+
"/usr/lib/aarch64-linux-gnu",
|
|
27
|
+
"/usr/lib/arm-linux-gnueabi",
|
|
28
|
+
"/usr/lib/arm-linux-gnueabihf",
|
|
29
|
+
"/usr/lib/i386-linux-gnu",
|
|
30
|
+
"/usr/lib/powerpc64le-linux-gnu",
|
|
31
|
+
"/usr/lib/riscv64-linux-gnu",
|
|
32
|
+
"/usr/lib/s390x-linux-gnu",
|
|
33
|
+
"/usr/lib/x86_64-linux-gnu",
|
|
34
|
+
]);
|
|
35
|
+
const MAX_ELF_BYTES = 256 * 1024 * 1024;
|
|
36
|
+
const MAX_PROGRAM_HEADERS = 4_096;
|
|
37
|
+
const MAX_MATERIALIZED_FILES = 1_024;
|
|
38
|
+
const MAX_DEPENDENCY_DEPTH = 64;
|
|
39
|
+
function isRecord(value) {
|
|
40
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41
|
+
}
|
|
42
|
+
function isNonEmptyText(value) {
|
|
43
|
+
return typeof value === "string" && value.length > 0 && !value.includes("\u0000");
|
|
44
|
+
}
|
|
45
|
+
function isStableIdentifier(value) {
|
|
46
|
+
return typeof value === "string" && /^[a-z0-9][a-z0-9._-]*$/u.test(value);
|
|
47
|
+
}
|
|
48
|
+
function compareText(left, right) {
|
|
49
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
50
|
+
}
|
|
51
|
+
function pathMatches(candidate, root) {
|
|
52
|
+
return candidate === root || candidate.startsWith(`${root}/`);
|
|
53
|
+
}
|
|
54
|
+
function isFhsPath(candidate, allowRoot = false) {
|
|
55
|
+
return FHS_RUNTIME_ROOTS.some((root) => (allowRoot && candidate === root) || pathMatches(candidate, root));
|
|
56
|
+
}
|
|
57
|
+
/** Strip a reason's own trailing period so it nests into a sentence exactly once. */
|
|
58
|
+
function reasonSentence(reason) {
|
|
59
|
+
return reason.endsWith(".") ? reason.slice(0, -1) : reason;
|
|
60
|
+
}
|
|
61
|
+
function materializationFailure(requirement, reason, details = {}) {
|
|
62
|
+
const canonical = runtimeMaterializationMissingError(requirement.id, requirement.kind);
|
|
63
|
+
return failure(new DomainError(canonical.code, `${canonical.message.slice(0, -1)}: ${reasonSentence(reason)}.`, { ...(canonical.details ?? {}), reason, ...details }, canonical.exitCode));
|
|
64
|
+
}
|
|
65
|
+
function ambiguousMaterialization(field, reason, value) {
|
|
66
|
+
return failure(new DomainError("RUNTIME_PROJECTION_AMBIGUOUS", `FHS runtime field '${field}' is ambiguous: ${reason}.`, {
|
|
67
|
+
field,
|
|
68
|
+
value,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
function invalidMaterialization(field, reason, value) {
|
|
72
|
+
return failure(new DomainError("RUNTIME_PROJECTION_INVALID", `FHS runtime field '${field}' is invalid: ${reason}.`, {
|
|
73
|
+
field,
|
|
74
|
+
...(value === undefined ? {} : { value }),
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
function profileContext(value) {
|
|
78
|
+
if (!isRecord(value) ||
|
|
79
|
+
value.contract_id !== RUNTIME_PROFILE_CONTRACT_ID ||
|
|
80
|
+
value.schema_version !== RUNTIME_PROFILE_SCHEMA_VERSION ||
|
|
81
|
+
!isRecord(value.profile) ||
|
|
82
|
+
!isStableIdentifier(value.profile.id) ||
|
|
83
|
+
!isNonEmptyText(value.profile.version)) {
|
|
84
|
+
return failure(new DomainError("RUNTIME_PROFILE_INVALID", "FHS materialization requires a resolved runtime profile."));
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(value.requirements)) {
|
|
87
|
+
return failure(new DomainError("RUNTIME_PROFILE_INVALID", "The resolved runtime profile requirements are invalid."));
|
|
88
|
+
}
|
|
89
|
+
const requirements = [];
|
|
90
|
+
const ids = new Set();
|
|
91
|
+
for (const [index, item] of value.requirements.entries()) {
|
|
92
|
+
if (!isRecord(item) ||
|
|
93
|
+
!isStableIdentifier(item.id) ||
|
|
94
|
+
(item.kind !== "runtime" && item.kind !== "package") ||
|
|
95
|
+
!isNonEmptyText(item.name) ||
|
|
96
|
+
!isNonEmptyText(item.version)) {
|
|
97
|
+
return failure(new DomainError("RUNTIME_PROFILE_INVALID", `Resolved runtime profile requirement ${index} is invalid.`, {
|
|
98
|
+
requirement_index: index,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
if (ids.has(item.id)) {
|
|
102
|
+
return ambiguousMaterialization("requirements", "duplicate requirement id", item.id);
|
|
103
|
+
}
|
|
104
|
+
ids.add(item.id);
|
|
105
|
+
requirements.push(Object.freeze({
|
|
106
|
+
id: item.id,
|
|
107
|
+
kind: item.kind,
|
|
108
|
+
name: item.name,
|
|
109
|
+
version: item.version,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
if (requirements.length === 0) {
|
|
113
|
+
return failure(new DomainError("RUNTIME_PROFILE_REQUIREMENT_MISSING", "The runtime profile has no material requirements."));
|
|
114
|
+
}
|
|
115
|
+
return success({
|
|
116
|
+
profile: Object.freeze({ id: value.profile.id, version: value.profile.version }),
|
|
117
|
+
requirements: Object.freeze(requirements),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function validateFhsTarget(candidate, field, allowRoot = false) {
|
|
121
|
+
if (!isNonEmptyText(candidate) || !posix.isAbsolute(candidate) || posix.normalize(candidate) !== candidate) {
|
|
122
|
+
return invalidMaterialization(field, "expected a normalized absolute POSIX path", typeof candidate === "string" ? candidate : undefined);
|
|
123
|
+
}
|
|
124
|
+
if (!isFhsPath(candidate, allowRoot)) {
|
|
125
|
+
return invalidMaterialization(field, "path is outside the bounded FHS roots", candidate);
|
|
126
|
+
}
|
|
127
|
+
return success(candidate);
|
|
128
|
+
}
|
|
129
|
+
function canonicalHostFile(candidate, requirement, field, allowExternalSource = false, inspect) {
|
|
130
|
+
try {
|
|
131
|
+
const inspected = inspectRuntimeFile(candidate, inspect ?? (() => undefined));
|
|
132
|
+
const source = inspected.source;
|
|
133
|
+
if (!allowExternalSource && !isFhsPath(source)) {
|
|
134
|
+
return materializationFailure(requirement, "declared artifact resolves outside the bounded FHS roots", {
|
|
135
|
+
field,
|
|
136
|
+
resolved_source: source,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const inspection = inspect === undefined ? undefined : inspected.value;
|
|
140
|
+
return success({ source, target: candidate, ...(inspection === undefined ? {} : { inspection }) });
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
return materializationFailure(requirement, "declared artifact is missing or cannot be canonicalized", {
|
|
144
|
+
field,
|
|
145
|
+
reason_detail: error instanceof Error ? error.message.slice(0, 120) : "unknown",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function canonicalHostDirectory(candidate, requirement, field) {
|
|
150
|
+
try {
|
|
151
|
+
const stat = fs.lstatSync(candidate);
|
|
152
|
+
if (!stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
153
|
+
return materializationFailure(requirement, "compatibility root is not a directory", { field });
|
|
154
|
+
}
|
|
155
|
+
const source = fs.realpathSync.native(candidate);
|
|
156
|
+
if (!fs.statSync(source).isDirectory() || !isFhsPath(source, true)) {
|
|
157
|
+
return materializationFailure(requirement, "compatibility root resolves outside the bounded FHS roots", {
|
|
158
|
+
field,
|
|
159
|
+
resolved_source: source,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return success(source);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
return materializationFailure(requirement, "compatibility root cannot be canonicalized", {
|
|
166
|
+
field,
|
|
167
|
+
reason_detail: error instanceof Error ? error.message.slice(0, 120) : "unknown",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function readInteger(data, offset, width) {
|
|
172
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset + width > data.length)
|
|
173
|
+
return null;
|
|
174
|
+
try {
|
|
175
|
+
if (width === 2)
|
|
176
|
+
return data.readUInt16LE(offset);
|
|
177
|
+
if (width === 4)
|
|
178
|
+
return data.readUInt32LE(offset);
|
|
179
|
+
const value = data.readBigUInt64LE(offset);
|
|
180
|
+
return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : null;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function boundedRange(offset, size, length) {
|
|
187
|
+
return (Number.isSafeInteger(offset) &&
|
|
188
|
+
Number.isSafeInteger(size) &&
|
|
189
|
+
offset >= 0 &&
|
|
190
|
+
size >= 0 &&
|
|
191
|
+
offset <= length &&
|
|
192
|
+
size <= length - offset);
|
|
193
|
+
}
|
|
194
|
+
function readString(data, offset, maxLength, allowEmpty = false) {
|
|
195
|
+
if (!boundedRange(offset, maxLength, data.length))
|
|
196
|
+
return null;
|
|
197
|
+
const end = data.indexOf(0, offset);
|
|
198
|
+
if (end === -1 || end >= offset + maxLength)
|
|
199
|
+
return null;
|
|
200
|
+
const value = data.subarray(offset, end).toString("utf8");
|
|
201
|
+
return (!allowEmpty && value.length === 0) || value.includes("\u0000") ? null : value;
|
|
202
|
+
}
|
|
203
|
+
function virtualToFileOffset(address, size, loads, dataLength) {
|
|
204
|
+
for (const load of loads) {
|
|
205
|
+
if (address >= load.virtual_address &&
|
|
206
|
+
address <= load.virtual_address + load.file_size &&
|
|
207
|
+
size <= load.file_size - (address - load.virtual_address)) {
|
|
208
|
+
const offset = load.offset + (address - load.virtual_address);
|
|
209
|
+
return boundedRange(offset, size, dataLength) ? offset : null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
function parseElf(data, requirement) {
|
|
215
|
+
if (data.length < 52 || data[0] !== 0x7f || data[1] !== 0x45 || data[2] !== 0x4c || data[3] !== 0x46) {
|
|
216
|
+
return materializationFailure(requirement, "unsupported non-ELF executable format");
|
|
217
|
+
}
|
|
218
|
+
const elfClass = data[4];
|
|
219
|
+
const dataEncoding = data[5];
|
|
220
|
+
if ((elfClass !== 1 && elfClass !== 2) || dataEncoding !== 1) {
|
|
221
|
+
return materializationFailure(requirement, "unsupported ELF class or byte order", {
|
|
222
|
+
elf_class: elfClass ?? -1,
|
|
223
|
+
elf_data: dataEncoding ?? -1,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const is64 = elfClass === 2;
|
|
227
|
+
const phoff = readInteger(data, is64 ? 32 : 28, is64 ? 8 : 4);
|
|
228
|
+
const phentsize = readInteger(data, is64 ? 54 : 42, 2);
|
|
229
|
+
const phnum = readInteger(data, is64 ? 56 : 44, 2);
|
|
230
|
+
if (phoff === null || phentsize === null || phnum === null || phnum > MAX_PROGRAM_HEADERS) {
|
|
231
|
+
return materializationFailure(requirement, "ELF program-header table is invalid");
|
|
232
|
+
}
|
|
233
|
+
const minimumPhentsize = is64 ? 56 : 32;
|
|
234
|
+
if (phentsize < minimumPhentsize || !boundedRange(phoff, phentsize * phnum, data.length)) {
|
|
235
|
+
return materializationFailure(requirement, "ELF program-header table is outside the file");
|
|
236
|
+
}
|
|
237
|
+
const headers = [];
|
|
238
|
+
for (let index = 0; index < phnum; index += 1) {
|
|
239
|
+
const offset = phoff + index * phentsize;
|
|
240
|
+
const type = readInteger(data, offset, 4);
|
|
241
|
+
const fileOffset = readInteger(data, offset + (is64 ? 8 : 4), is64 ? 8 : 4);
|
|
242
|
+
const virtualAddress = readInteger(data, offset + (is64 ? 16 : 8), is64 ? 8 : 4);
|
|
243
|
+
const fileSize = readInteger(data, offset + (is64 ? 32 : 16), is64 ? 8 : 4);
|
|
244
|
+
const memorySize = readInteger(data, offset + (is64 ? 40 : 20), is64 ? 8 : 4);
|
|
245
|
+
if (type === null ||
|
|
246
|
+
fileOffset === null ||
|
|
247
|
+
virtualAddress === null ||
|
|
248
|
+
fileSize === null ||
|
|
249
|
+
memorySize === null ||
|
|
250
|
+
!boundedRange(fileOffset, fileSize, data.length)) {
|
|
251
|
+
return materializationFailure(requirement, "ELF program header contains an invalid range");
|
|
252
|
+
}
|
|
253
|
+
headers.push({
|
|
254
|
+
type,
|
|
255
|
+
offset: fileOffset,
|
|
256
|
+
virtual_address: virtualAddress,
|
|
257
|
+
file_size: fileSize,
|
|
258
|
+
memory_size: memorySize,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const loads = headers.filter((header) => header.type === 1);
|
|
262
|
+
const interpreterHeaders = headers.filter((header) => header.type === 3);
|
|
263
|
+
if (interpreterHeaders.length > 1)
|
|
264
|
+
return materializationFailure(requirement, "ELF has multiple program interpreters");
|
|
265
|
+
let interpreter = null;
|
|
266
|
+
if (interpreterHeaders.length === 1) {
|
|
267
|
+
const header = interpreterHeaders[0];
|
|
268
|
+
interpreter = readString(data, header.offset, header.file_size);
|
|
269
|
+
if (interpreter === null)
|
|
270
|
+
return materializationFailure(requirement, "ELF interpreter string is invalid");
|
|
271
|
+
}
|
|
272
|
+
const dynamic = headers.find((header) => header.type === 2);
|
|
273
|
+
if (dynamic === undefined)
|
|
274
|
+
return success({ interpreter, needed: Object.freeze([]), rpath: null, runpath: null });
|
|
275
|
+
const dynamicEntrySize = is64 ? 16 : 8;
|
|
276
|
+
if (dynamic.file_size < dynamicEntrySize || dynamic.file_size % dynamicEntrySize !== 0) {
|
|
277
|
+
return materializationFailure(requirement, "ELF dynamic section has an invalid size");
|
|
278
|
+
}
|
|
279
|
+
let stringTableAddress = null;
|
|
280
|
+
let stringTableSize = null;
|
|
281
|
+
const neededOffsets = [];
|
|
282
|
+
let rpathOffset = null;
|
|
283
|
+
let runpathOffset = null;
|
|
284
|
+
let hasNull = false;
|
|
285
|
+
for (let offset = dynamic.offset; offset < dynamic.offset + dynamic.file_size; offset += dynamicEntrySize) {
|
|
286
|
+
const tag = readInteger(data, offset, is64 ? 8 : 4);
|
|
287
|
+
const value = readInteger(data, offset + (is64 ? 8 : 4), is64 ? 8 : 4);
|
|
288
|
+
if (tag === null || value === null)
|
|
289
|
+
return materializationFailure(requirement, "ELF dynamic entry is invalid");
|
|
290
|
+
if (tag === 0) {
|
|
291
|
+
hasNull = true;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
if (tag === 1)
|
|
295
|
+
neededOffsets.push(value);
|
|
296
|
+
if (tag === 5)
|
|
297
|
+
stringTableAddress = value;
|
|
298
|
+
if (tag === 10)
|
|
299
|
+
stringTableSize = value;
|
|
300
|
+
if (tag === 15) {
|
|
301
|
+
if (rpathOffset !== null)
|
|
302
|
+
return materializationFailure(requirement, "ELF has multiple DT_RPATH entries");
|
|
303
|
+
rpathOffset = value;
|
|
304
|
+
}
|
|
305
|
+
if (tag === 29) {
|
|
306
|
+
if (runpathOffset !== null)
|
|
307
|
+
return materializationFailure(requirement, "ELF has multiple DT_RUNPATH entries");
|
|
308
|
+
runpathOffset = value;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (!hasNull)
|
|
312
|
+
return materializationFailure(requirement, "ELF dynamic section has no terminator");
|
|
313
|
+
if (neededOffsets.length === 0 && rpathOffset === null && runpathOffset === null)
|
|
314
|
+
return success({ interpreter, needed: Object.freeze([]), rpath: null, runpath: null });
|
|
315
|
+
if (stringTableAddress === null || stringTableSize === null || stringTableSize === 0) {
|
|
316
|
+
return materializationFailure(requirement, "ELF shared-library names have no valid string table");
|
|
317
|
+
}
|
|
318
|
+
const stringTableOffset = virtualToFileOffset(stringTableAddress, stringTableSize, loads, data.length);
|
|
319
|
+
if (stringTableOffset === null)
|
|
320
|
+
return materializationFailure(requirement, "ELF string table is not file-backed");
|
|
321
|
+
const readSearchPath = (offset, tag) => {
|
|
322
|
+
if (offset === null)
|
|
323
|
+
return null;
|
|
324
|
+
if (offset >= stringTableSize) {
|
|
325
|
+
throw new Error(`${tag} string offset is out of range`);
|
|
326
|
+
}
|
|
327
|
+
const value = readString(data, stringTableOffset + offset, stringTableSize - offset, true);
|
|
328
|
+
if (value === null)
|
|
329
|
+
throw new Error(`${tag} string is invalid`);
|
|
330
|
+
return value;
|
|
331
|
+
};
|
|
332
|
+
let rpath;
|
|
333
|
+
let runpath;
|
|
334
|
+
try {
|
|
335
|
+
rpath = readSearchPath(rpathOffset, "DT_RPATH");
|
|
336
|
+
runpath = readSearchPath(runpathOffset, "DT_RUNPATH");
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
return materializationFailure(requirement, error instanceof Error ? error.message : "ELF search path is invalid");
|
|
340
|
+
}
|
|
341
|
+
if (neededOffsets.length === 0) {
|
|
342
|
+
return success({ interpreter, needed: Object.freeze([]), rpath, runpath });
|
|
343
|
+
}
|
|
344
|
+
const needed = [];
|
|
345
|
+
for (const neededOffset of neededOffsets) {
|
|
346
|
+
if (neededOffset >= stringTableSize)
|
|
347
|
+
return materializationFailure(requirement, "ELF shared-library name is out of range");
|
|
348
|
+
const name = readString(data, stringTableOffset + neededOffset, stringTableSize - neededOffset);
|
|
349
|
+
if (name === null)
|
|
350
|
+
return materializationFailure(requirement, "ELF shared-library name is invalid");
|
|
351
|
+
needed.push(name);
|
|
352
|
+
}
|
|
353
|
+
return success({ interpreter, needed: Object.freeze(needed), rpath, runpath });
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* FHS package entrypoints may be executable scripts (notably pnpm). Their
|
|
357
|
+
* shebang interpreter is another bounded artifact; the script itself has no
|
|
358
|
+
* ELF loader or DT_NEEDED closure to inspect.
|
|
359
|
+
*/
|
|
360
|
+
function parseScriptInterpreter(data, requirement) {
|
|
361
|
+
const firstLine = data.subarray(0, 4_096).toString("utf8").split("\n", 1)[0] ?? "";
|
|
362
|
+
if (!firstLine.startsWith("#!"))
|
|
363
|
+
return success(null);
|
|
364
|
+
const shebang = firstLine.slice(2).trim();
|
|
365
|
+
const interpreter = shebang.split(/\s+/u, 1)[0] ?? "";
|
|
366
|
+
if (interpreter.length === 0 || !posix.isAbsolute(interpreter) || posix.normalize(interpreter) !== interpreter) {
|
|
367
|
+
return materializationFailure(requirement, "script interpreter is not a bounded absolute path");
|
|
368
|
+
}
|
|
369
|
+
return success(interpreter);
|
|
370
|
+
}
|
|
371
|
+
function inspectArtifact(descriptor, stat, requirement) {
|
|
372
|
+
if (stat.size > BigInt(MAX_ELF_BYTES)) {
|
|
373
|
+
return materializationFailure(requirement, "ELF artifact exceeds the bounded inspection size", {
|
|
374
|
+
artifact_size: Number(stat.size),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
let data;
|
|
378
|
+
try {
|
|
379
|
+
data = readRuntimeFile(descriptor, stat.size, MAX_ELF_BYTES);
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
return materializationFailure(requirement, "ELF artifact cannot be read", {
|
|
383
|
+
reason_detail: error instanceof Error ? error.message.slice(0, 120) : "unknown",
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
const parsed = parseElf(data, requirement);
|
|
387
|
+
if (parsed.ok)
|
|
388
|
+
return parsed;
|
|
389
|
+
const scriptInterpreter = parseScriptInterpreter(data, requirement);
|
|
390
|
+
if (!scriptInterpreter.ok || scriptInterpreter.value === null)
|
|
391
|
+
return parsed;
|
|
392
|
+
return success({ interpreter: scriptInterpreter.value, needed: Object.freeze([]), rpath: null, runpath: null });
|
|
393
|
+
}
|
|
394
|
+
function validateLibrarySearchPaths(value) {
|
|
395
|
+
if (value === undefined)
|
|
396
|
+
return success(Object.freeze([...FHS_RUNTIME_LIBRARY_SEARCH_PATHS].sort(compareText)));
|
|
397
|
+
if (!Array.isArray(value))
|
|
398
|
+
return invalidMaterialization("library_search_paths", "expected an array");
|
|
399
|
+
const paths = new Set();
|
|
400
|
+
for (const [index, item] of value.entries()) {
|
|
401
|
+
const checked = validateFhsTarget(item, `library_search_paths[${index}]`, true);
|
|
402
|
+
if (!checked.ok)
|
|
403
|
+
return checked;
|
|
404
|
+
let boundedPath = checked.value;
|
|
405
|
+
try {
|
|
406
|
+
const stat = fs.statSync(checked.value);
|
|
407
|
+
if (!stat.isDirectory())
|
|
408
|
+
return invalidMaterialization(`library_search_paths[${index}]`, "path is not a directory", checked.value);
|
|
409
|
+
const resolved = fs.realpathSync.native(checked.value);
|
|
410
|
+
if (!isFhsPath(resolved, true)) {
|
|
411
|
+
return invalidMaterialization(`library_search_paths[${index}]`, "directory resolves outside FHS roots", resolved);
|
|
412
|
+
}
|
|
413
|
+
boundedPath = resolved;
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
if (error.code !== "ENOENT") {
|
|
417
|
+
return invalidMaterialization(`library_search_paths[${index}]`, `directory cannot be inspected${error instanceof Error ? ` (${error.message.slice(0, 120)})` : ""}`, checked.value);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
paths.add(boundedPath);
|
|
421
|
+
}
|
|
422
|
+
return success(Object.freeze([...paths].sort(compareText)));
|
|
423
|
+
}
|
|
424
|
+
function validateExecutableDeclarations(value, requirements) {
|
|
425
|
+
if (!Array.isArray(value))
|
|
426
|
+
return invalidMaterialization("executables", "expected an array");
|
|
427
|
+
const requirementMap = new Map(requirements.map((requirement) => [requirement.id, requirement]));
|
|
428
|
+
const declarations = new Map();
|
|
429
|
+
for (const [index, item] of value.entries()) {
|
|
430
|
+
if (!isRecord(item) || !isStableIdentifier(item.requirement_id) || !isNonEmptyText(item.path)) {
|
|
431
|
+
return invalidMaterialization("executables", "declaration requires a stable requirement_id and path", String(index));
|
|
432
|
+
}
|
|
433
|
+
const requirement = requirementMap.get(item.requirement_id);
|
|
434
|
+
if (requirement === undefined) {
|
|
435
|
+
return failure(new DomainError("RUNTIME_MATERIALIZATION_MISSING", `Executable declaration references unknown requirement '${item.requirement_id}'.`, {
|
|
436
|
+
requirement_id: item.requirement_id,
|
|
437
|
+
requirement_kind: "runtime",
|
|
438
|
+
}));
|
|
439
|
+
}
|
|
440
|
+
const source = item.path;
|
|
441
|
+
if (!posix.isAbsolute(source) || posix.normalize(source) !== source) {
|
|
442
|
+
return invalidMaterialization(`executables[${index}].path`, "expected a normalized absolute POSIX path", source);
|
|
443
|
+
}
|
|
444
|
+
const target = item.target === undefined
|
|
445
|
+
? validateFhsTarget(source, `executables[${index}].path`)
|
|
446
|
+
: validateFhsTarget(item.target, `executables[${index}].target`);
|
|
447
|
+
if (!target.ok)
|
|
448
|
+
return target;
|
|
449
|
+
if (declarations.has(item.requirement_id)) {
|
|
450
|
+
return ambiguousMaterialization("executables.requirement_id", "multiple artifacts satisfy one requirement", item.requirement_id);
|
|
451
|
+
}
|
|
452
|
+
declarations.set(item.requirement_id, Object.freeze({
|
|
453
|
+
requirement_id: requirement.id,
|
|
454
|
+
path: source,
|
|
455
|
+
...(item.target === undefined ? {} : { target: target.value }),
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
for (const requirement of requirements) {
|
|
459
|
+
if (!declarations.has(requirement.id)) {
|
|
460
|
+
return failure(runtimeMaterializationMissingError(requirement.id, requirement.kind));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return success(declarations);
|
|
464
|
+
}
|
|
465
|
+
function safeLibraryName(name) {
|
|
466
|
+
return name.length > 0 && !name.includes("\u0000") && !name.startsWith(".") && !name.includes("/") && name !== "..";
|
|
467
|
+
}
|
|
468
|
+
function canonicalSearchDirectory(candidate, requirement, field) {
|
|
469
|
+
let probe = candidate;
|
|
470
|
+
const missingTail = [];
|
|
471
|
+
for (;;) {
|
|
472
|
+
try {
|
|
473
|
+
const stat = fs.statSync(probe);
|
|
474
|
+
if (!stat.isDirectory()) {
|
|
475
|
+
return materializationFailure(requirement, "ELF search path is not a directory", {
|
|
476
|
+
field,
|
|
477
|
+
search_path: candidate,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const resolved = fs.realpathSync.native(probe);
|
|
481
|
+
if (!isFhsPath(resolved, true)) {
|
|
482
|
+
return materializationFailure(requirement, "ELF search path resolves outside the bounded FHS roots", {
|
|
483
|
+
field,
|
|
484
|
+
search_path: candidate,
|
|
485
|
+
resolved_source: resolved,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const bounded = missingTail.reduce((directory, part) => posix.join(directory, part), resolved);
|
|
489
|
+
if (!isFhsPath(bounded, true)) {
|
|
490
|
+
return materializationFailure(requirement, "ELF search path escapes the bounded FHS roots", {
|
|
491
|
+
field,
|
|
492
|
+
search_path: candidate,
|
|
493
|
+
resolved_source: bounded,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return success(bounded);
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
const code = error.code;
|
|
500
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
501
|
+
return materializationFailure(requirement, "ELF search path cannot be canonicalized", {
|
|
502
|
+
field,
|
|
503
|
+
search_path: candidate,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
const parent = posix.dirname(probe);
|
|
507
|
+
if (parent === probe) {
|
|
508
|
+
return materializationFailure(requirement, "ELF search path cannot be canonicalized", {
|
|
509
|
+
field,
|
|
510
|
+
search_path: candidate,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
missingTail.unshift(posix.basename(probe));
|
|
514
|
+
probe = parent;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function boundedElfSearchPaths(value, objectSource, requirement, field) {
|
|
519
|
+
if (value === null || value.length === 0)
|
|
520
|
+
return success(Object.freeze([]));
|
|
521
|
+
const origin = posix.dirname(objectSource);
|
|
522
|
+
const paths = [];
|
|
523
|
+
const seen = new Set();
|
|
524
|
+
for (const [index, entry] of value.split(":").entries()) {
|
|
525
|
+
if (entry.length === 0) {
|
|
526
|
+
return materializationFailure(requirement, "ELF search path contains an ambient empty element", {
|
|
527
|
+
field,
|
|
528
|
+
search_path_index: index,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
const expanded = entry.replaceAll("$ORIGIN", origin);
|
|
532
|
+
if (expanded.includes("$")) {
|
|
533
|
+
return materializationFailure(requirement, "ELF search path contains an unsupported token", {
|
|
534
|
+
field,
|
|
535
|
+
search_path: entry,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
if (!posix.isAbsolute(expanded)) {
|
|
539
|
+
return materializationFailure(requirement, "ELF search path is not absolute after bounded expansion", {
|
|
540
|
+
field,
|
|
541
|
+
search_path: entry,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
const normalized = posix.normalize(expanded);
|
|
545
|
+
if (!isFhsPath(normalized, true)) {
|
|
546
|
+
return materializationFailure(requirement, "ELF search path escapes the bounded FHS roots", {
|
|
547
|
+
field,
|
|
548
|
+
search_path: entry,
|
|
549
|
+
resolved_source: normalized,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
const bounded = canonicalSearchDirectory(normalized, requirement, field);
|
|
553
|
+
if (!bounded.ok)
|
|
554
|
+
return bounded;
|
|
555
|
+
if (seen.has(bounded.value))
|
|
556
|
+
continue;
|
|
557
|
+
seen.add(bounded.value);
|
|
558
|
+
paths.push(bounded.value);
|
|
559
|
+
}
|
|
560
|
+
return success(Object.freeze(paths));
|
|
561
|
+
}
|
|
562
|
+
function canonicalDependency(candidate, requirement, field) {
|
|
563
|
+
const target = validateFhsTarget(candidate, field);
|
|
564
|
+
if (!target.ok) {
|
|
565
|
+
return materializationFailure(requirement, "required dependency path is outside the bounded FHS roots", {
|
|
566
|
+
dependency: candidate,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
try {
|
|
570
|
+
fs.lstatSync(target.value);
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
if (error.code === "ENOENT") {
|
|
574
|
+
return materializationFailure(requirement, "required dependency is missing", { dependency: target.value });
|
|
575
|
+
}
|
|
576
|
+
return materializationFailure(requirement, "required dependency cannot be inspected", {
|
|
577
|
+
dependency: target.value,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
return canonicalHostFile(target.value, requirement, field);
|
|
581
|
+
}
|
|
582
|
+
function resolveLibrary(name, searchPaths, requirement) {
|
|
583
|
+
if (name.startsWith("/"))
|
|
584
|
+
return canonicalDependency(name, requirement, "dependency");
|
|
585
|
+
if (!safeLibraryName(name))
|
|
586
|
+
return materializationFailure(requirement, "shared-library name is not a safe basename", { dependency: name });
|
|
587
|
+
for (const directory of searchPaths) {
|
|
588
|
+
const candidate = posix.join(directory, name);
|
|
589
|
+
try {
|
|
590
|
+
fs.lstatSync(candidate);
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
if (error.code === "ENOENT")
|
|
594
|
+
continue;
|
|
595
|
+
return materializationFailure(requirement, "shared-library candidate cannot be inspected", { dependency: name });
|
|
596
|
+
}
|
|
597
|
+
return canonicalHostFile(candidate, requirement, "dependency");
|
|
598
|
+
}
|
|
599
|
+
return materializationFailure(requirement, "shared library was not found in bounded FHS search paths", {
|
|
600
|
+
dependency: name,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Resolve declared FHS executables and their ELF loader/library closure into
|
|
605
|
+
* file-level read-only projections. No directory or host-root projection is
|
|
606
|
+
* ever generated by this function.
|
|
607
|
+
*/
|
|
608
|
+
export function materializeFhsRuntime(input) {
|
|
609
|
+
if (!isRecord(input))
|
|
610
|
+
return invalidMaterialization("materialization", "expected an object");
|
|
611
|
+
const context = profileContext(input.profile);
|
|
612
|
+
if (!context.ok)
|
|
613
|
+
return context;
|
|
614
|
+
const policy = validateRuntimePolicy(input.policy ?? STRICT_RUNTIME_POLICY);
|
|
615
|
+
if (!policy.ok)
|
|
616
|
+
return policy;
|
|
617
|
+
const searchPaths = validateLibrarySearchPaths(input.library_search_paths);
|
|
618
|
+
if (!searchPaths.ok)
|
|
619
|
+
return searchPaths;
|
|
620
|
+
const declarations = validateExecutableDeclarations(input.executables, context.value.requirements);
|
|
621
|
+
if (!declarations.ok)
|
|
622
|
+
return declarations;
|
|
623
|
+
const requirements = new Map(context.value.requirements.map((requirement) => [
|
|
624
|
+
requirement.id,
|
|
625
|
+
Object.freeze({
|
|
626
|
+
id: requirement.id,
|
|
627
|
+
kind: requirement.kind,
|
|
628
|
+
provenance: policy.value.mode === "compatibility"
|
|
629
|
+
? "compatibility"
|
|
630
|
+
: requirement.kind === "package"
|
|
631
|
+
? "package"
|
|
632
|
+
: "runtime-profile",
|
|
633
|
+
}),
|
|
634
|
+
]));
|
|
635
|
+
const mounts = new Map();
|
|
636
|
+
const parsed = new Map();
|
|
637
|
+
const processed = new Set();
|
|
638
|
+
if (policy.value.mode === "compatibility") {
|
|
639
|
+
const compatibilityRequirement = [...requirements.values()].sort((left, right) => compareText(left.id, right.id))[0];
|
|
640
|
+
for (const declaration of declarations.value.values()) {
|
|
641
|
+
const requirement = requirements.get(declaration.requirement_id);
|
|
642
|
+
const checked = canonicalHostFile(declaration.path, requirement, "executables.path");
|
|
643
|
+
if (!checked.ok)
|
|
644
|
+
return checked;
|
|
645
|
+
}
|
|
646
|
+
for (const root of FHS_RUNTIME_ROOTS) {
|
|
647
|
+
let present = false;
|
|
648
|
+
try {
|
|
649
|
+
fs.lstatSync(root);
|
|
650
|
+
present = true;
|
|
651
|
+
}
|
|
652
|
+
catch (error) {
|
|
653
|
+
if (error.code !== "ENOENT") {
|
|
654
|
+
return materializationFailure(compatibilityRequirement, "compatibility root cannot be inspected", {
|
|
655
|
+
root,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (!present)
|
|
660
|
+
continue;
|
|
661
|
+
const source = canonicalHostDirectory(root, compatibilityRequirement, "filesystem.source");
|
|
662
|
+
if (!source.ok)
|
|
663
|
+
return source;
|
|
664
|
+
mounts.set(root, Object.freeze({
|
|
665
|
+
source: source.value,
|
|
666
|
+
target: root,
|
|
667
|
+
access_mode: "read-only",
|
|
668
|
+
provenance: "compatibility",
|
|
669
|
+
}));
|
|
670
|
+
}
|
|
671
|
+
if (mounts.size === 0) {
|
|
672
|
+
return materializationFailure(compatibilityRequirement, "no FHS compatibility roots are available");
|
|
673
|
+
}
|
|
674
|
+
return validateSessionRuntimeProjection({
|
|
675
|
+
policy: policy.value,
|
|
676
|
+
profile: context.value.profile,
|
|
677
|
+
requirements: context.value.requirements,
|
|
678
|
+
filesystem: [...mounts.values()],
|
|
679
|
+
executables: [],
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
const addArtifact = (candidate, target, requirement, depth, inheritedRpath, allowExternalSource = false) => {
|
|
683
|
+
if (depth > MAX_DEPENDENCY_DEPTH)
|
|
684
|
+
return materializationFailure(requirement, "dependency graph exceeds the bounded depth");
|
|
685
|
+
const checkedTarget = validateFhsTarget(target, "filesystem.target");
|
|
686
|
+
if (!checkedTarget.ok)
|
|
687
|
+
return checkedTarget;
|
|
688
|
+
const source = canonicalHostFile(candidate, requirement, "filesystem.source", allowExternalSource, (descriptor, stat) => inspectArtifact(descriptor, stat, requirement));
|
|
689
|
+
if (!source.ok)
|
|
690
|
+
return source;
|
|
691
|
+
const previous = mounts.get(checkedTarget.value);
|
|
692
|
+
if (previous !== undefined) {
|
|
693
|
+
if (previous.source !== source.value.source) {
|
|
694
|
+
return ambiguousMaterialization("filesystem.target", "target resolves to multiple host files", checkedTarget.value);
|
|
695
|
+
}
|
|
696
|
+
return success(null);
|
|
697
|
+
}
|
|
698
|
+
if (mounts.size >= MAX_MATERIALIZED_FILES) {
|
|
699
|
+
return materializationFailure(requirement, "dependency graph exceeds the bounded file count");
|
|
700
|
+
}
|
|
701
|
+
mounts.set(checkedTarget.value, Object.freeze({
|
|
702
|
+
source: source.value.source,
|
|
703
|
+
target: checkedTarget.value,
|
|
704
|
+
access_mode: "read-only",
|
|
705
|
+
provenance: requirement.provenance,
|
|
706
|
+
}));
|
|
707
|
+
const cacheKey = `${source.value.source}\u0000${checkedTarget.value}`;
|
|
708
|
+
if (processed.has(cacheKey))
|
|
709
|
+
return success(null);
|
|
710
|
+
processed.add(cacheKey);
|
|
711
|
+
let metadata = parsed.get(source.value.source);
|
|
712
|
+
if (metadata === undefined) {
|
|
713
|
+
const inspected = source.value.inspection;
|
|
714
|
+
if (inspected === undefined) {
|
|
715
|
+
return materializationFailure(requirement, "ELF artifact inspection did not produce metadata");
|
|
716
|
+
}
|
|
717
|
+
if (!inspected.ok)
|
|
718
|
+
return inspected;
|
|
719
|
+
metadata = inspected.value;
|
|
720
|
+
parsed.set(source.value.source, metadata);
|
|
721
|
+
}
|
|
722
|
+
const rpath = boundedElfSearchPaths(metadata.rpath, source.value.source, requirement, "DT_RPATH");
|
|
723
|
+
if (!rpath.ok)
|
|
724
|
+
return rpath;
|
|
725
|
+
const runpath = boundedElfSearchPaths(metadata.runpath, source.value.source, requirement, "DT_RUNPATH");
|
|
726
|
+
if (!runpath.ok)
|
|
727
|
+
return runpath;
|
|
728
|
+
const objectSearchPaths = [
|
|
729
|
+
...(metadata.runpath === null ? rpath.value : runpath.value),
|
|
730
|
+
...inheritedRpath,
|
|
731
|
+
...searchPaths.value,
|
|
732
|
+
];
|
|
733
|
+
const childInheritedRpath = metadata.runpath === null ? [...rpath.value, ...inheritedRpath] : [...inheritedRpath];
|
|
734
|
+
if (metadata.interpreter !== null) {
|
|
735
|
+
const interpreter = canonicalDependency(metadata.interpreter, requirement, "interpreter");
|
|
736
|
+
if (!interpreter.ok)
|
|
737
|
+
return interpreter;
|
|
738
|
+
const addedInterpreter = addArtifact(interpreter.value.source, interpreter.value.target, requirement, depth + 1, childInheritedRpath);
|
|
739
|
+
if (!addedInterpreter.ok)
|
|
740
|
+
return addedInterpreter;
|
|
741
|
+
}
|
|
742
|
+
for (const needed of [...metadata.needed].sort(compareText)) {
|
|
743
|
+
const dependency = resolveLibrary(needed, objectSearchPaths, requirement);
|
|
744
|
+
if (!dependency.ok)
|
|
745
|
+
return dependency;
|
|
746
|
+
const addedDependency = addArtifact(dependency.value.source, dependency.value.target, requirement, depth + 1, childInheritedRpath);
|
|
747
|
+
if (!addedDependency.ok)
|
|
748
|
+
return addedDependency;
|
|
749
|
+
}
|
|
750
|
+
return success(null);
|
|
751
|
+
};
|
|
752
|
+
for (const profileRequirement of [...context.value.requirements].sort((left, right) => compareText(left.id, right.id))) {
|
|
753
|
+
const declaration = declarations.value.get(profileRequirement.id);
|
|
754
|
+
const requirement = requirements.get(profileRequirement.id);
|
|
755
|
+
const target = validateFhsTarget(declaration.target ?? declaration.path, `executables.${profileRequirement.id}.target`);
|
|
756
|
+
if (!target.ok)
|
|
757
|
+
return target;
|
|
758
|
+
const added = addArtifact(declaration.path, target.value, requirement, 0, [], declaration.target !== undefined);
|
|
759
|
+
if (!added.ok)
|
|
760
|
+
return added;
|
|
761
|
+
}
|
|
762
|
+
const projection = validateSessionRuntimeProjection({
|
|
763
|
+
policy: policy.value,
|
|
764
|
+
profile: context.value.profile,
|
|
765
|
+
requirements: context.value.requirements,
|
|
766
|
+
filesystem: [...mounts.values()],
|
|
767
|
+
// Executable aliases are intentionally left to the later provider contract.
|
|
768
|
+
executables: [],
|
|
769
|
+
});
|
|
770
|
+
if (!projection.ok)
|
|
771
|
+
return projection;
|
|
772
|
+
return projection;
|
|
773
|
+
}
|
|
774
|
+
/** Descriptive alias for callers that emphasize the resulting projection. */
|
|
775
|
+
export const materializeFhsRuntimeProjection = materializeFhsRuntime;
|
|
776
|
+
//# sourceMappingURL=fhs-runtime.js.map
|