-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathpost_install.cppm
More file actions
531 lines (493 loc) · 25.5 KB
/
Copy pathpost_install.cppm
File metadata and controls
531 lines (493 loc) · 25.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
// mcpp.toolchain.post_install — toolchain payload post-install fixups (patchelf / specs / cfg)
//
// Extracted verbatim from cli.cppm (cli modularization, see
// .agents/docs/2026-06-10-cli-modularization.md). Zero behavior change:
// bodies are byte-identical moves; only the surrounding module/namespace
// changed (mcpp::cli::detail -> mcpp::cli).
module;
#include <cstdio>
#include <cstdlib>
export module mcpp.toolchain.post_install;
import std;
import mcpp.config;
import mcpp.libs.json;
import mcpp.log;
import mcpp.platform;
import mcpp.toolchain.linkmodel;
import mcpp.toolchain.registry;
import mcpp.ui;
import mcpp.xlings;
namespace mcpp::toolchain {
// ── #273 sandbox containment ─────────────────────────────────────────────
// patchelf_walk once escaped its sandbox through a symlinked payload
// directory (an e2e sandbox seeded `registry/data/xpkgs/xim-x-gcc` from the
// user's real ~/.xlings via `ln -s`) and rewrote PT_INTERP/RUNPATH of the
// REAL installation against loader paths inside a soon-deleted mktemp dir.
//
// Containment rule: every rewrite in this module is fenced by ONE explicit
// trust root — the owning sandbox's registry (`cfg.registryDir`), resolved
// once at the entry point via `containment_root` and threaded down as a
// parameter. The root is a first-class fact of the system; it is never
// re-derived from the payload path (canonicalizing the payload first would
// resolve the malicious symlink and collapse the fence into a tautology).
export std::filesystem::path
containment_root(const std::filesystem::path& registryDir) {
std::error_code ec;
auto canon = std::filesystem::weakly_canonical(registryDir, ec);
if (ec) return {}; // empty root → escapes_containment fails closed
return canon;
}
// True when `file`'s physical (symlink-resolved) location falls outside
// `rootCanon`. Fails CLOSED: an empty root or an unresolvable path counts
// as escaping — never patch what cannot be proven contained. Comparison is
// per path component (a `registry-evil` sibling sharing the string prefix
// is outside).
export bool escapes_containment(const std::filesystem::path& file,
const std::filesystem::path& rootCanon) {
if (rootCanon.empty()) return true;
std::error_code ec;
auto real = std::filesystem::weakly_canonical(file, ec);
if (ec) return true;
auto r = rootCanon.generic_string();
auto f = real.generic_string();
if (f == r) return false;
return !(f.size() > r.size() && f.starts_with(r) && f[r.size()] == '/');
}
// Run patchelf on every dynamic ELF in `dir` (recursively):
// - Set PT_INTERP to `loader` (the sandbox-local glibc loader).
// - Set RUNPATH to `rpath` (colon-separated list of sandbox lib dirs).
// Idempotent; skips static binaries and shared libs without PT_INTERP.
//
// TODO(xlings/libxpkg-upstream): xim 0.4.10's `elfpatch.auto({interpreter=...})`
// is supposed to do this in install hooks but currently scans 0 files for
// some packages (verified empirically: `binutils: elfpatch auto: 0 0 0`).
// Once the upstream legacy elfpatch path is fixed, this mcpp-side walker
// can be deleted.
export void patchelf_walk(const std::filesystem::path& dir,
const std::filesystem::path& loader,
const std::string& rpath,
const std::filesystem::path& patchelfBin,
const std::filesystem::path& fenceRoot)
{
if (!std::filesystem::exists(dir) || !std::filesystem::exists(patchelfBin))
return;
std::error_code ec;
for (auto it = std::filesystem::recursive_directory_iterator(dir, ec);
it != std::filesystem::recursive_directory_iterator{}; it.increment(ec))
{
if (ec) { ec.clear(); continue; }
if (!it->is_regular_file(ec)) continue;
auto path = it->path();
// Skip non-ELF (cheap magic check)
std::ifstream is(path, std::ios::binary);
char m[4]{};
is.read(m, 4);
if (!is || m[0] != 0x7f || m[1] != 'E' || m[2] != 'L' || m[3] != 'F')
continue;
is.close();
// #273 fence: never rewrite files that physically live outside the
// sandbox (payload reached through a symlink → foreign installation).
if (escapes_containment(path, fenceRoot)) {
mcpp::log::verbose("toolchain", std::format(
"patchelf_walk: skip (outside sandbox, #273 fence): {}",
path.string()));
continue;
}
// Probe PT_INTERP — skip static binaries (no interp).
auto probe = std::format("{} --print-interpreter {} 2>/dev/null",
mcpp::platform::shell::quote(patchelfBin.string()),
mcpp::platform::shell::quote(path.string()));
auto probeResult = mcpp::platform::process::capture(probe);
bool hasInterp = (probeResult.exit_code == 0 && !probeResult.output.empty());
// Patch a COPY and atomically rename it into place. The payload can
// contain libraries the CURRENT process has mmapped (a self-hosted
// mcpp links the sandbox glibc/libgcc_s, and since the fixup
// pipeline runs on every install path, the patching process may BE
// such a consumer). In-place patchelf rewrites the backing file of
// those live mappings and corrupts the running process — observed
// on CI as an exit-time SIGSEGV in _dl_fini jumping to an
// unrelocated address. rename() gives the patched content a fresh
// inode while live processes keep the old one.
auto tmp = path;
tmp += ".mcpp-patch.tmp";
{
std::error_code cec;
std::filesystem::copy_file(
path, tmp, std::filesystem::copy_options::overwrite_existing, cec);
if (cec) continue;
std::filesystem::permissions(
tmp, std::filesystem::status(path, cec).permissions(),
std::filesystem::perm_options::replace, cec);
}
bool patched = true;
if (hasInterp) {
patched = (mcpp::platform::process::run_silent(std::format(
"{} --set-interpreter {} {} 2>/dev/null",
mcpp::platform::shell::quote(patchelfBin.string()),
mcpp::platform::shell::quote(loader.string()),
mcpp::platform::shell::quote(tmp.string()))) == 0) && patched;
}
// Always set RUNPATH (works on .so too — they need to find deps).
if (!rpath.empty()) {
patched = (mcpp::platform::process::run_silent(std::format(
"{} --set-rpath {} {} 2>/dev/null",
mcpp::platform::shell::quote(patchelfBin.string()),
mcpp::platform::shell::quote(rpath),
mcpp::platform::shell::quote(tmp.string()))) == 0) && patched;
}
std::error_code rec;
if (patched) std::filesystem::rename(tmp, path, rec);
if (!patched || rec) std::filesystem::remove(tmp, rec);
}
}
// xim bakes the installing user's XLINGS_HOME into gcc specs at install
// time (as `--dynamic-linker` and `-rpath`). When mcpp uses its own
// isolated sandbox (MCPP_HOME/registry/), the baked-in paths point to
// xlings' home, not mcpp's sandbox glibc — binaries would fail to exec.
//
// Mcpp does a post-install spec rewrite:
// - Dynamically detects the baked-in loader path from the specs file
// - Replaces it with the sandbox glibc payload's loader
// - Replaces the rpath with <glibc_lib>:<gcc_lib64>
// Idempotent — skips if already pointing at the correct glibc.
// Extract the baked-in glibc loader path (".../ld-linux-<arch>.so.N") from a
// gcc specs file. xim bakes the installing user's XLINGS_HOME into specs at
// install time, so the DIR varies per machine, and the loader NAME varies
// per arch — detect both instead of hardcoding either.
export std::string detect_baked_loader(const std::string& specsContent) {
// Path-character whitelist. Specs embed loader paths inside %-spec
// syntax (`%{mmusl:...;:/baked/dir/ld-linux-x86-64.so.2}`), so scanning
// to "whitespace or :;" is NOT a valid boundary — it would swallow the
// closing braces, and replacing that string corrupts the spec grammar
// ("braced spec body ... is invalid" from every subsequent g++ run).
auto is_path_char = [](char c) {
return std::isalnum(static_cast<unsigned char>(c))
|| c == '/' || c == '.' || c == '-' || c == '_' || c == '+';
};
// The baked GNU loader is the ld-linux entry whose directory is NOT a
// standard /lib* location — specs also contain pristine defaults
// (/lib/ld-linux.so.2, /libx32/…) for other multilib branches that must
// never be rewritten.
constexpr std::string_view kLoaderMark = "/ld-linux-";
for (std::size_t pos = specsContent.find(kLoaderMark);
pos != std::string::npos;
pos = specsContent.find(kLoaderMark, pos + 1)) {
auto start = pos;
while (start > 0 && is_path_char(specsContent[start - 1])) --start;
auto end = pos + 1;
while (end < specsContent.size() && is_path_char(specsContent[end])) ++end;
auto loader = specsContent.substr(start, end - start);
if (loader.empty() || loader[0] != '/') continue;
auto dir = std::filesystem::path(loader).parent_path().string();
if (dir == "/lib" || dir == "/lib64" || dir == "/lib32" || dir == "/libx32")
continue; // pristine multilib default, not a baked path
return loader;
}
return "";
}
void fixup_gcc_specs(const std::filesystem::path& gccPkgRoot,
const std::filesystem::path& glibcLibDir,
const std::filesystem::path& gccLibDir,
const std::filesystem::path& fenceRoot)
{
// #273 fence: a payload reached through a symlink is a foreign
// installation — its specs file must not be rewritten either.
if (escapes_containment(gccPkgRoot, fenceRoot)) {
mcpp::log::verbose("toolchain",
"fixup_gcc_specs: skip (payload outside sandbox, #273 fence)");
return;
}
std::filesystem::path specsParent;
std::error_code ec;
for (auto it = std::filesystem::directory_iterator(gccPkgRoot / "lib" / "gcc", ec);
!ec && it != std::filesystem::directory_iterator{}; it.increment(ec)) {
if (it->is_directory(ec)) { specsParent = it->path(); break; }
}
if (specsParent.empty()) return;
auto loaderReplacement = resolve_loader(glibcLibDir, /*targetTriple=*/{}).string();
if (loaderReplacement.empty()) return;
auto rpathReplacement = std::format("{}:{}",
glibcLibDir.string(),
gccLibDir.string());
auto replace_all = [](std::string& s, std::string_view needle,
std::string_view rep)
{
for (std::size_t pos = 0;
(pos = s.find(needle, pos)) != std::string::npos;) {
s.replace(pos, needle.size(), rep);
pos += rep.size();
}
};
for (auto& sub : std::filesystem::directory_iterator(specsParent)) {
auto specs = sub.path() / "specs";
if (!std::filesystem::exists(specs)) continue;
std::ifstream is(specs);
std::stringstream ss; ss << is.rdbuf();
std::string content = ss.str();
auto bakedLoader = detect_baked_loader(content);
if (bakedLoader.empty()) continue;
auto bakedDir = std::filesystem::path(bakedLoader).parent_path().string();
// Already pointing at the right place — no fixup needed.
if (bakedDir == glibcLibDir.string()) continue;
// Order matters: replace the full loader file path first so the
// shorter dir pattern doesn't eat its prefix.
replace_all(content, bakedLoader, loaderReplacement);
replace_all(content, bakedDir, rpathReplacement);
std::ofstream os(specs);
os << content;
}
}
// Regenerate the clang driver cfg files after the LLVM payload landed in the
// sandbox. The cfg xlings authored at install time is a per-machine,
// per-install-path artifact (its content depended on what existed when the
// package was installed); mcpp's builds bypass it entirely
// (--no-default-config), so its only remaining job is to make a HUMAN
// running `clang++` directly get a working, hermetic compiler. We therefore
// regenerate it deterministically from the same link model the builds use,
// instead of line-patching whatever a given install produced:
// C + C++: -B/-L glibc payload, payload dynamic linker + rpath,
// lld / compiler-rt / libunwind
// C++ only: -nostdinc++ -stdlib=libc++ + payload libc++ headers/libs
// On macOS the C library comes from the SDK: --sysroot=<sdk> + libc++ headers.
export void fixup_clang_cfg(const std::filesystem::path& payloadRoot,
const std::filesystem::path& glibcLibDir,
const std::filesystem::path& fenceRoot) {
// #273 fence — same rule as fixup_gcc_specs above.
if (escapes_containment(payloadRoot, fenceRoot)) {
mcpp::log::verbose("toolchain",
"fixup_clang_cfg: skip (payload outside sandbox, #273 fence)");
return;
}
auto binDir = payloadRoot / "bin";
if (!std::filesystem::exists(binDir)) return;
// Target triple from the payload layout (lib/<triple>), used for the
// loader lookup and the per-target libc++ include/lib dirs.
std::string triple;
std::error_code ec;
for (auto it = std::filesystem::directory_iterator(payloadRoot / "lib", ec);
!ec && it != std::filesystem::directory_iterator{}; it.increment(ec)) {
auto name = it->path().filename().string();
if (it->is_directory(ec) && name.find("-linux-") != std::string::npos) {
triple = name;
break;
}
}
std::string common, cxxOnly, cHdr;
auto cxxInclude = payloadRoot / "include" / "c++" / "v1";
if constexpr (mcpp::platform::is_macos) {
// macOS keeps its historical cfg semantics: the C library and the
// C++ runtime LINK both come from the SDK; only the libc++ HEADERS
// come from the payload. Do NOT add -nostdinc++/-stdlib=libc++
// here — a bare cfg-driven link has no libc++abi handling (that
// lives in the main build's needs_explicit_libcxx path) and dies
// with undefined __cxa_* / __gxx_personality_v0.
if (auto sdk = mcpp::platform::macos::sdk_path())
common += "--sysroot=" + sdk->string() + "\n";
if (std::filesystem::exists(cxxInclude))
cxxOnly += "-isystem " + cxxInclude.string() + "\n";
} else {
if (!glibcLibDir.empty()) {
auto loader = resolve_loader(glibcLibDir, triple);
common += "-B" + glibcLibDir.string() + "\n";
common += "-L" + glibcLibDir.string() + "\n";
if (!loader.empty())
common += "-Wl,--dynamic-linker=" + loader.string() + "\n";
common += "-Wl,--enable-new-dtags,-rpath," + glibcLibDir.string() + "\n";
}
common += "-fuse-ld=lld\n--rtlib=compiler-rt\n--unwindlib=libunwind\n";
// HEADER axis (C and C++ drivers alike): the C library and kernel
// headers come from the same payloads the link axis uses. Without
// these, a direct `clang hello.c` only works when the HOST happens
// to ship /usr/include — silently non-hermetic, broken on
// header-less machines. For C++ they must come AFTER the libc++
// block (its C-header wrappers reach libc via #include_next), so
// they are collected separately and appended in order below —
// byte-consistent with what llvm.lua's install hook generates.
if (!glibcLibDir.empty()) {
auto glibcInclude = glibcLibDir.parent_path() / "include";
if (std::filesystem::exists(glibcInclude / "features.h"))
cHdr += "-isystem " + glibcInclude.string() + "\n";
constexpr std::string_view kLinuxLimits = "include/linux/limits.h";
auto linuxHeaders = mcpp::xlings::paths::find_sibling_package(
payloadRoot / "bin" / "clang++", "linux-headers", kLinuxLimits);
if (!linuxHeaders)
linuxHeaders = mcpp::xlings::paths::find_home_tool(
"linux-headers", kLinuxLimits);
if (linuxHeaders)
cHdr += "-isystem " + (*linuxHeaders / "include").string() + "\n";
}
if (std::filesystem::exists(cxxInclude)) {
cxxOnly += "-nostdinc++\n-stdlib=libc++\n";
cxxOnly += "-isystem " + cxxInclude.string() + "\n";
}
if (!triple.empty()) {
auto tripleInclude = payloadRoot / "include" / triple / "c++" / "v1";
if (std::filesystem::exists(tripleInclude))
cxxOnly += "-isystem " + tripleInclude.string() + "\n";
}
cxxOnly += cHdr;
if (!triple.empty()) {
auto tripleLib = payloadRoot / "lib" / triple;
if (std::filesystem::exists(tripleLib)) {
cxxOnly += "-L" + tripleLib.string() + "\n";
cxxOnly += "-Wl,-rpath," + tripleLib.string() + "\n";
}
}
}
// Regenerate every existing cfg in bin/ (clang.cfg, clang++.cfg, and any
// versioned clang-<major>.cfg xlings created), classified C vs C++ by
// whether the driver name contains "++".
for (auto it = std::filesystem::directory_iterator(binDir, ec);
!ec && it != std::filesystem::directory_iterator{}; it.increment(ec)) {
auto name = it->path().filename().string();
if (!name.ends_with(".cfg")) continue;
const bool isCxx = name.find("++") != std::string::npos;
std::ofstream os(it->path());
os << common << (isCxx ? cxxOnly : cHdr);
}
}
// Locate the sandbox glibc payload's lib dir (the newest installed version
// that actually carries a dynamic loader). Shared by the gcc and llvm fixups.
std::filesystem::path find_sandbox_glibc_lib(const mcpp::xlings::Env& xlEnv) {
auto glibcRoot = mcpp::xlings::paths::xim_tool_root(xlEnv, "glibc");
std::error_code ec;
for (auto it = std::filesystem::directory_iterator(glibcRoot, ec);
!ec && it != std::filesystem::directory_iterator{}; it.increment(ec)) {
if (auto lib = payload_lib_dir_with_loader(it->path()); !lib.empty())
return lib;
}
return {};
}
// Post-install fixup for a freshly-installed GNU gcc payload: patchelf
// PT_INTERP/RUNPATH for gcc/binutils binaries + linker-specs wiring against
// the sandbox glibc — without it a fresh-sandbox glibc gcc cannot find the
// C library (stdlib.h not found).
void gcc_post_install_fixup(const mcpp::config::GlobalConfig& cfg,
const std::filesystem::path& payloadRoot,
const std::filesystem::path& glibcLibDir) {
auto xlEnv = mcpp::config::make_xlings_env(cfg);
auto gccLibDir = payloadRoot / "lib64";
auto patchelfBin = mcpp::xlings::paths::xim_tool(xlEnv, "patchelf",
mcpp::xlings::pinned::kPatchelfVersion) / "bin" / "patchelf";
if (!glibcLibDir.empty() && std::filesystem::exists(gccLibDir)
&& std::filesystem::exists(patchelfBin))
{
auto loader = resolve_loader(glibcLibDir, /*targetTriple=*/{});
auto rpath = std::format("{}:{}",
glibcLibDir.string(), gccLibDir.string());
mcpp::log::verbose("toolchain", std::format(
"gcc fixup: patchelf_walk rpath='{}'", rpath));
// Single trust root for every walk below — including the binutils
// SIBLING payloads, which the entry-point ownership guard does not
// cover (it only vets the gcc payload itself).
auto fence = containment_root(cfg.registryDir);
auto binutilsRoot = mcpp::xlings::paths::xim_tool_root(xlEnv, "binutils");
if (std::filesystem::exists(binutilsRoot)) {
for (auto& v : std::filesystem::directory_iterator(binutilsRoot))
patchelf_walk(v.path(), loader, rpath, patchelfBin, fence);
}
patchelf_walk(payloadRoot, loader, rpath, patchelfBin, fence);
mcpp::log::verbose("toolchain", "gcc fixup: fixup_gcc_specs");
fixup_gcc_specs(payloadRoot, glibcLibDir, gccLibDir,
containment_root(cfg.registryDir));
} else {
mcpp::ui::warning(
"could not locate sandbox glibc/gcc/patchelf paths; "
"gcc-built binaries may have unresolved PT_INTERP/RUNPATH");
}
}
// LLVM payload fixup: RUNPATH for the bundled runtime shared libraries
// (libc++.so / libunwind.so need to find siblings like libatomic.so.1 after
// the payload moved) + deterministic cfg regeneration. Only lib/ dirs are
// walked — NOT bin/: the clang++ binary's own RUNPATH (zlib, libxml2, …) was
// set by xlings and must be preserved.
void llvm_post_install_fixup(const mcpp::config::GlobalConfig& cfg,
const std::filesystem::path& payloadRoot,
const std::filesystem::path& glibcLibDir) {
auto xlEnv = mcpp::config::make_xlings_env(cfg);
auto patchelfBin = mcpp::xlings::paths::xim_tool(xlEnv, "patchelf",
mcpp::xlings::pinned::kPatchelfVersion) / "bin" / "patchelf";
if (!glibcLibDir.empty() && std::filesystem::exists(patchelfBin)) {
auto loader = resolve_loader(glibcLibDir, /*targetTriple=*/{});
auto llvmLib = payloadRoot / "lib";
std::string rpath;
std::error_code ec;
for (auto it = std::filesystem::directory_iterator(llvmLib, ec);
!ec && it != std::filesystem::directory_iterator{}; it.increment(ec)) {
if (it->is_directory(ec)
&& it->path().filename().string().find("-linux-") != std::string::npos)
rpath += it->path().string() + ":";
}
rpath += llvmLib.string() + ":" + glibcLibDir.string();
mcpp::log::verbose("toolchain", std::format(
"llvm fixup: patchelf_walk lib/ rpath='{}'", rpath));
patchelf_walk(llvmLib, loader, rpath, patchelfBin,
containment_root(cfg.registryDir));
}
mcpp::log::verbose("toolchain", "llvm fixup: fixup_clang_cfg");
fixup_clang_cfg(payloadRoot, glibcLibDir, containment_root(cfg.registryDir));
}
// ── the single fixup pipeline entry ──────────────────────────────────────
//
// Called from the payload-resolution seam shared by ALL toolchain install
// paths (explicit `mcpp toolchain install`, default-toolchain auto-install,
// and manifest `[toolchain]` auto-install). Previously each path remembered
// (or forgot) its own subset of fixups: the manifest path ran none, which is
// how a fresh llvm install kept a stale install-time cfg and unpatched
// runtime libs. Idempotent via a content-fingerprinted marker.
//
// Bump when the fixup logic changes so existing installs re-run it.
constexpr std::string_view kFixupRev = "hermetic-3";
export void ensure_post_install_fixup(const mcpp::config::GlobalConfig& cfg,
const std::filesystem::path& payloadRoot,
const XimToolchainPackage& pkg) {
std::string kind;
if (pkg.needsGccPostInstallFixup) kind = "gcc";
else if (pkg.ximName == "llvm") kind = "llvm";
else return;
if constexpr (mcpp::platform::is_windows) return; // PE world: no fixups
// Ownership guard: payloads inherited via symlink from another MCPP_HOME
// are not ours to patch — their owner already ran the fixup, and patching
// through the symlink would rewrite the canonical files against OUR
// (possibly ephemeral) paths, bricking the owner's toolchain.
// Rewritten on the shared containment predicate (#273): the old inline
// check reused one error_code across both canonicalizations (the second
// success cleared the first failure) and compared raw strings without a
// component boundary (a `registry-evil` sibling passed). The predicate
// fails closed on any resolution error.
if (escapes_containment(payloadRoot, containment_root(cfg.registryDir))) {
mcpp::log::verbose("toolchain", std::format(
"skip {} fixup: payload '{}' resolves outside this home — "
"inherited payload, owner is responsible for its fixup",
kind, payloadRoot.string()));
return;
}
auto xlEnv = mcpp::config::make_xlings_env(cfg);
std::filesystem::path glibcLibDir;
if constexpr (mcpp::platform::is_linux)
glibcLibDir = find_sandbox_glibc_lib(xlEnv);
// Content-fingerprinted marker: a marker whose INPUTS drifted (different
// glibc payload, newer fixup logic) re-runs the fixup — "a process once
// exited 0" is not evidence the current inputs were ever applied.
auto markerPath = payloadRoot / ".mcpp-fixup.json";
nlohmann::json expected;
expected["schema"] = 1;
expected["kind"] = kind;
expected["rev"] = std::string(kFixupRev);
expected["glibcLib"] = glibcLibDir.generic_string();
{
std::ifstream is(markerPath);
if (is) {
try {
nlohmann::json actual;
is >> actual;
if (actual == expected) return; // fixup already applied
} catch (...) { /* corrupt marker → re-run */ }
}
}
if (kind == "gcc") gcc_post_install_fixup(cfg, payloadRoot, glibcLibDir);
else llvm_post_install_fixup(cfg, payloadRoot, glibcLibDir);
std::ofstream os(markerPath);
os << expected.dump(2) << "\n";
}
} // namespace mcpp::toolchain