feat(elfpatch,loader): leave static and foreign ELF alone, honour set{skip,scan}, and a sink for build-script output (0.0.59) - #44
Merged
Conversation
…{skip,scan}, and a sink for build-script output (0.0.59)
elfpatch (libxpkg#43)
The automatic patch handed every ELF in a payload to patchelf. A file with
neither PT_INTERP nor DT_NEEDED -- a static or static-pie program, or a
loader itself -- has nothing a loader or an RPATH could resolve, and
`patchelf --set-rpath` on one produces a binary that segfaults before main.
Measured on the ChatGPT desktop payload: its bundled codex, rg and node_repl
helpers are static-pie, and after the patch each exited 139, which left the
app unable to start its app-server.
Every patching loop now asks one gate before touching a file:
* nothing to resolve (no PT_INTERP, no DT_NEEDED) -> skipped
* built for another machine than the loader being written
(EI_CLASS + e_machine of the loader's own header; the host's when there
is no loader, and no filtering on an architecture not recognised by
name) -> skipped
* listed in the recipe's `set{ skip = ... }` -> skipped
The headers are read in Lua (program headers, then the PT_DYNAMIC table)
instead of asking patchelf, one open per file. A header that cannot be read
in full is UNKNOWN and patched exactly as before: a skip needs positive
evidence, which also keeps an ELF-magic-only stand-in on its old path.
`set{ scan = ... }` narrows the fallback walk to the listed paths. Both
lists are paths relative to the install dir; a directory covers everything
beneath it; no wildcards. Results carry a `skipped` count. The dead
`_is_elf_for_host` and its helpers go.
loader
build_index(repo_dir, ns, BuildOutput) hands whatever a pkgindex-build.lua
writes with io.write / print to the caller, unchanged and in order, instead
of fd 1. The index build scripts draw a self-refreshing "\r[i/n] ...\033[K"
line, and only the caller knows whether its output is a terminal; written
to a file or a pipe those frames arrive as carriage returns and escape
sequences (openxlings/xlings#629). The two-argument form is unchanged.
Tests: ElfpatchGate_LeavesStaticForeignAndSkippedFilesAlone and
ElfpatchGate_ScanLimitsTheWalk build minimal well-formed ELF64 files (so the
verdict does not depend on the runner's machine), and
BuildIndex_PkgindexBuild_OutputGoesToTheSink asserts both that the sink
receives the frames and that stdout receives nothing.
Sunrisepeak
added a commit
to mcpplibs/mcpp-index
that referenced
this pull request
Sep 28, 2026
libxpkg 0.0.59 (openxlings/libxpkg#44): elfpatch leaves static, static-pie and foreign-machine ELF alone and honours set{skip,scan} (libxpkg#43), and build_index hands an index build script's output to the caller (openxlings/xlings#629). Read by xlings 2026.9.29.1. GitHub tag archive and GitCode mirror mcpp-res/xpkg are byte-identical (sha256 3998a623...0190), in all three platform blocks.
Sunrisepeak
pushed a commit
to openxlings/xlings
that referenced
this pull request
Sep 28, 2026
…ess that belongs to the renderer (#633) Closes #629. Closes #632 (§1, §4; §3 through openxlings/xim-pkgindex#903; §2 unchanged by decision). Consumes libxpkg 0.0.59 (openxlings/libxpkg#44, closing libxpkg#43). Install configures once per scope (#632 §1) - A payload in the store is a HOME fact; having run its config() is a fact about ONE scope. Each scope's .xlings.json records `configured: {"<ns>:<name>@<version>": <revision>}`, a top-level sibling of `workspace` (an older client reads every `workspace` key as a target). - xim::configured_verdict, asked by the planner and the installer: a present payload is left alone only when the record names the recipe's current revision AND every ledger entry the payload owns is claimed by the scope's installed[]. No record means configure -- old homes migrate on their next install. - A closure that is configured throughout does the report, the activation and one routing-table rebuild, and says how to configure again. `--reconfig` (interface `reconfig`, protocol 1.4) runs config for the whole plan. - A revision bump reaches every scope: the scope that reinstalls the payload records the new revision, every other scope's record names the old one. uninstall (detach and delete) and a superseded unbind erase the record. - Each node that did something prints ` [i/n] installed|configured <coord>`; the interface gets `progress` phase `configure`. - The privileged-env notice is printed for a new or changed declaration only (#632 §4). SubosRuntimeUnknown's remedy says `--reconfig`. Routing-table rebuild - Kept per node: later hooks of one plan run earlier nodes' commands by name (musl-gcc -> patchelf, gcc config -> <bindir>/gcc-specs-config, ...). Measured instead where a rebuild spent its time: re-parsing the 3.6 MB home config to read `knownProjects`, and in project scope rewriting it for `lastSeen`. known_projects() now follows the file's size/mtime and takes the list from the parses that already happen; register_known_project writes at most once a day. Output (#629) - An index build script's io.write/print reach xlings through libxpkg's BuildOutput, not fd 1: one `[index] built <ns> (<n> files)` line, the script's other lines passed on, the interface's index_rebuild events unchanged (parse_bracketed_step moved to xim::index, one implementation). - index:* downloads draw no frames; a finished progress block ends with a blank line; the renderer asks the frontend's capability, so `--ui-mode cli` means no bars (palette::cursor_rewrite_allowed, the second answerer, is gone); `self update` runs its children with `--ui-mode cli`. Tests: E2E-125 install_configured_record_test.sh (C1 fails on 2026.9.28.2), E2E-126 index_build_output_test.sh (B1 fails on 2026.9.28.2), E2E-123 P2 extended; unit ConfiguredVerdict.*, ProgressOutput.* rewritten on ui::capabilities_of. Docs: interface spec 1.4, AGENTS.md, quick-start, xlings-usage skill, plan and release notes under .agents/docs/. Build - mcpp 2026.9.28.3 in .xlings.json and XIM_PKGINDEX_REF 7b9bf43 in all six workflows: the mcpplibs index requires mcpp >= 2026.9.18.3 (E0006), and libxpkg 0.0.59 is the first xpkg this repository resolves through it rather than from a warm payload cache. mcpp.lock carries the xpkg hash as that mcpp computes it. - log arguments are std::string where a ternary of literals made them `const char*`: libc++ (llvm 20.1.7) deduced a wide format string for them and commands.cpp did not compile on macOS/Windows; reproduced and verified with `mcpp build --toolchain llvm@20.1.7` on Linux.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #43. Needed by openxlings/xlings#629 (and the xlings 2026.9.29.1 release that consumes it).
elfpatch
Every patching loop (declared bins/libs and the fallback scan) asks one gate before handing a file to patchelf:
PT_INTERPand noDT_NEEDEDPT_DYNAMICtable--set-rpathon one segfaults before main (measured: ChatGPT's bundledrg,codex,node_repl→ exit 139)EI_CLASS+e_machinevs the loader being written (host when there is none; no filter on an unrecognised host)set{ skip = ... }A header that cannot be read in full is unknown and patched as before — a skip needs positive evidence.
set{ scan = ... }narrows the fallback walk. Results gainskipped. The unused_is_elf_for_host/_read_e_machine/_host_e_machineare removed.Verified on real files with the new reader: static-pie
bwrapfrom the codex payload →interp=false needed=0(andpatchelf --set-rpathon a copy → exit 139); glibc'sld-linux-x86-64.so.2→false/0(skipped: never patch the loader);libc.so.6→true/1;/usr/bin/ls→true/2; aarch64 musllibc.so→ machine 183 (foreign on x86_64); a PE.nodeand an ELF-magic-only stub → unknown.loader
build_index(repo_dir, ns, BuildOutput)routes apkgindex-build.lua'sio.write/printto the caller instead of fd 1. The two-argument form is unchanged.Tests
mcpp test -- --gtest_filter=-ExecutorTest.ApplyElfpatchAuto_*(CI's filter): 138 passed. New:ElfpatchGate_LeavesStaticForeignAndSkippedFilesAlone,ElfpatchGate_ScanLimitsTheWalk(minimal well-formed ELF64s, so the verdict does not depend on the runner),BuildIndex_PkgindexBuild_OutputGoesToTheSink. The eight filteredApplyElfpatchAuto_*also pass locally with this change.