From d84dbbe49aa5803249f23eb7f140cd4faddad2e5 Mon Sep 17 00:00:00 2001 From: Nemo <328747105+Nemo-010@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:52:58 +0000 Subject: [PATCH 1/2] Reach the NixOS host directories Greetings from Port Edwards. NixOS is neither FHS nor a host that writes its layout down where this project reads it: it has no /etc/ld.so.conf, /usr/lib holds no host libraries, and hardware.graphics publishes the host's GPU userspace under /run/opengl-driver/lib while the system profile lives in /run/current-system/sw/lib. Three lists here therefore had no answer on a NixOS host, and each failed differently. - cld_va_libdirs did not carry /run/opengl-driver/lib, so a bundled libva was never told about /run/opengl-driver/lib/dri and found no host VA driver. - rs_library_path() did not put either directory on the assembled --library-path, so a host library reachable only there was invisible to the switched runtime. - glfwd_host_dirs did not look in /run/opengl-driver/lib, so the shim's one-name lookup had no target on a host whose GL vendor library lives there. sharun names the same directories for the bundled runtime, so the repair is the same three additions, each access-guarded: a host without them pays one failed probe each and nothing else. E104, E105 and E106 are the cases, one per list. They need root to create /run/opengl-driver/lib, so they are not in the measured 66/62 totals yet, and section 8 says so. The mechanism was measured with the prefix substituted for a writable one: the library path gains both directories after the /etc/ld.so.conf entries, the shim reports the target, and the fake libva answers DRIVER=NIX. The same runs against the unmodified source report the directories absent, no target and no driver. Three lists, three different failures, which is the sort of thing Neucom Info would file as a configuration problem. We aim to provide the software that shapes the world of tomorrow. --- docs/integrating.md | 4 +- docs/report/08-test-results.md | 8 ++++ docs/report/09-the-second-boundary.md | 34 +++++++++++++++++ experiments/30-run-tests.sh | 54 +++++++++++++++++++++++++++ src/cross-libc-dlopen.c | 7 +++- src/gl-fwd.c | 7 ++++ src/runtime-select.c | 12 ++++++ 7 files changed, 123 insertions(+), 3 deletions(-) diff --git a/docs/integrating.md b/docs/integrating.md index 3ae18af..3d67c19 100644 --- a/docs/integrating.md +++ b/docs/integrating.md @@ -136,7 +136,9 @@ place and no `--library-path` can fix it. When `libva.so.2` is in the process, the preload assembles the answer: every host `/dri` directory it finds is appended to `LIBVA_DRIVERS_PATH`, -behind anything already set. A process that never loads libva is not touched. +behind anything already set. NixOS is one of those ``s, and its VA +drivers live under `/run/opengl-driver/lib/dri`. A process that never loads +libva is not touched. The bundle's own `lib/dri` is never added. A bundle that ships VA drivers manages the variable itself, and its entries stay ahead of anything appended here. diff --git a/docs/report/08-test-results.md b/docs/report/08-test-results.md index fb85a30..699269f 100644 --- a/docs/report/08-test-results.md +++ b/docs/report/08-test-results.md @@ -31,6 +31,11 @@ total minus the four skips below, and CI re-runs both on every push. `experiments/run.ps1` drives the same three stage scripts for a machine with PowerShell and no POSIX shell. +⚠ **E104, E105 and E106 are not in those totals yet.** They are the cases for +the NixOS host directories (section 9.22), they need root to create +`/run/opengl-driver/lib`, and the machine that added them had no root. The +next container run measures them and moves the totals to 69/69 and 65/65. + ⚠ **The two totals differ by exactly the four cases aarch64 SKIPS**, each naming the capability it lacks rather than the difference being unexplained: @@ -76,6 +81,9 @@ objects small enough that the mechanism is the only thing being measured: | E73 | the distinct-name call count an application can be measured by | | E75 / E75b | the shim finds a target in a directory only `/etc/ld.so.conf` names, and does not when the conf file is removed | | E103 | a directory the host names first holds a wrong-class library: the shim skips it and finds the one that loads, instead of reporting no target | +| E104 | the switched runtime's `--library-path` carries `/run/opengl-driver/lib` and `/run/current-system/sw/lib`, which NixOS publishes and no `ld.so.conf` names | +| E105 | the shim finds a target under `/run/opengl-driver/lib`, the directory a NixOS host keeps its GPU userspace in | +| E106 | `LIBVA_DRIVERS_PATH` gets `/run/opengl-driver/lib/dri`, the directory a NixOS host keeps its VA drivers in | | E75c / E75d / E75e / E75f | a shim with no target serves each name from the provider behind it in the lookup order; the no-preload control; the log line pinning the fallthrough; the same in EAGER mode | | E76 / E76b | the aarch64 trampolines and resolver RUN, under qemu-user, forwarding and absent paths both | diff --git a/docs/report/09-the-second-boundary.md b/docs/report/09-the-second-boundary.md index 28dcbc6..e25d533 100644 --- a/docs/report/09-the-second-boundary.md +++ b/docs/report/09-the-second-boundary.md @@ -1531,6 +1531,40 @@ E103 is the case. It uses the five-name miniature of section O, with the repair the shim reports `no target`; after it, it reports the `libtgt.so` in the directory named second and the probe passes. +### 9.22 NixOS is a host class the FHS lists cannot reach + +NixOS is neither FHS nor a host that writes its layout down where this project +reads it. It has no `/etc/ld.so.conf`, `/usr/lib` holds no host libraries, and +`hardware.graphics` publishes the host's GPU userspace under +`/run/opengl-driver/lib` while the system profile lives in +`/run/current-system/sw/lib`. Three lists here therefore had no answer on a +NixOS host, and each failed differently: + +- `LIBVA_DRIVERS_PATH` never received `/run/opengl-driver/lib/dri`, so a + bundled libva found no host VA driver at all. +- `rs_library_path()` never put the two directories on the assembled + `--library-path`, so a host library reachable only there was invisible to the + switched runtime. +- `glfwd_host_dirs` never looked in `/run/opengl-driver/lib`, so the shim's + one-name lookup had no target on a host whose GL vendor library lives there. + +sharun appends the same directories for the bundled runtime, so the repair is +the same three additions here. `/run/opengl-driver/lib` joins +`cld_va_libdirs` and is probed as `/dri`, the two directories are appended +to the library path after the `/etc/ld.so.conf` entries, and both join the +shim's fixed host list. Every entry is access-guarded, so a host without them +pays one failed probe each and nothing else. + +E104, E105 and E106 are the cases. They need root to create +`/run/opengl-driver/lib`, which the machine that added them could not do, so +they are not in the 66/62 totals in section 8 until the container suite runs +them. The mechanism was measured on that machine with the prefix substituted +for a writable one: the assembled library path gains both directories after the +`/etc/ld.so.conf` ones, the shim reports `target +.../opengl-driver/lib/libtgt.so`, and the fake libva answers `DRIVER=NIX` from a +driver that exists only in the substituted directory. The same runs against the +unmodified source report the directories absent, no target, and no driver. + --- --- diff --git a/experiments/30-run-tests.sh b/experiments/30-run-tests.sh index f4f5de5..37a63ff 100644 --- a/experiments/30-run-tests.sh +++ b/experiments/30-run-tests.sh @@ -379,6 +379,35 @@ else run E21 OK "runtime : host" \ env APPDIR="$PWD/app_old" ./runtime-select --probe --host-dir "$PWD/goodhost" + # E104: the NixOS directories are on the assembled library path. NixOS is + # not FHS and writes no /etc/ld.so.conf: hardware.graphics publishes + # the host's GPU userspace under /run/opengl-driver/lib and the system + # profile is /run/current-system/sw/lib. sharun appends both, and the + # switched runtime has to as well or a host library that lives only + # there is invisible. + if mkdir -p /run/opengl-driver/lib /run/current-system/sw/lib 2>/dev/null; then + run E104 OK "OK: both NixOS dirs on the library path" \ + env APP="$PWD/app_old" HOSTD="$PWD/goodhost" sh -c ' + lp=$(env APPDIR="$APP" ./runtime-select --probe \ + --host-dir "$HOSTD" --no-self-test | + sed -n "s/^library-path : //p") + case ":$lp:" in + *:/run/opengl-driver/lib:*) a=yes ;; + *) a=no ;; + esac + case ":$lp:" in + *:/run/current-system/sw/lib:*) b=yes ;; + *) b=no ;; + esac + if [ "$a" = yes ] && [ "$b" = yes ]; then + echo "OK: both NixOS dirs on the library path" + else + echo "MISSING: $lp" + fi' + else + echo " E104 SKIPPED - cannot create /run/opengl-driver/lib: not root" + fi + # ---- the build-time strict-environment option ------------------------- # # APPDIR is a convention this project does not own: an AppImage runtime @@ -1057,6 +1086,18 @@ run E103 OK "target /opt/cross-libc-unguessable-42/libtgt.so" fwd_noenv ./tramp2 noconf run E75b FAIL "no target; all 5 entry points return zero" fwd_noenv ./tramp2 +# E105: NixOS publishes the host's GPU userspace under /run/opengl-driver/lib +# and writes no /etc/ld.so.conf naming it. The shim's fixed host list has +# to carry that directory, or a NixOS host has no target at all. The conf +# file is still gone, from E75b's control, so nothing else names it. +if mkdir -p /run/opengl-driver/lib 2>/dev/null && + cp libtgt.so /run/opengl-driver/lib/libtgt.so 2>/dev/null; then + run E105 OK "target /run/opengl-driver/lib/libtgt.so" fwd_noenv ./tramp2 + rm -f /run/opengl-driver/lib/libtgt.so +else + echo " E105 SKIPPED - cannot write /run/opengl-driver/lib: not root" +fi + # E75c: a shim with no target must not shadow the providers behind it. The # preload wins the lookup, so with tgt-fwd.so preloaded the binary's # t_ints binds to the shim whatever else the process carries, and @@ -1418,6 +1459,19 @@ run E99 OK "bundle-dri-absent" sh -c \ run E100 OK "env=[(unset)]" \ env LD_PRELOAD=/work/cross-libc-dlopen.so ./va_nolib +# E106: NixOS publishes the host's VA drivers under /run/opengl-driver/lib/dri, +# which is neither FHS nor named by /etc/ld.so.conf, so only a list that +# carries it can answer. LD_LIBRARY_PATH is cleared, because a directory +# on the process's own search list is not what this case is about. +if mkdir -p /run/opengl-driver/lib/dri 2>/dev/null; then + gcc -shared -fPIC -O2 -DVA_VENDOR='"NIX"' va_drv.c \ + -o /run/opengl-driver/lib/dri/vastub_drv_video.so 2>"$BERR" || bfail vastub-NIX + run E106 OK "DRIVER=NIX env=[/run/opengl-driver/lib/dri" \ + env -u LD_LIBRARY_PATH LD_PRELOAD=/work/cross-libc-dlopen.so ./va_consumer +else + echo " E106 SKIPPED - cannot create /run/opengl-driver/lib/dri: not root" +fi + echo echo "================================================================" echo " predictions matched: $PASS mismatched: $FAIL" diff --git a/src/cross-libc-dlopen.c b/src/cross-libc-dlopen.c index 0c24b5d..2da9e9d 100644 --- a/src/cross-libc-dlopen.c +++ b/src/cross-libc-dlopen.c @@ -1490,9 +1490,12 @@ VISIBLE void cross_libc_dlopen_init_now(void) { // The conventional libdirs of the hosts this runs on, each probed as // /dri. Debian and Ubuntu keep their VA drivers under the triplet -// directory, Alpine in /usr/lib/dri, Fedora in /usr/lib64/dri. A missed -// access() costs nothing. +// directory, Alpine in /usr/lib/dri, Fedora in /usr/lib64/dri. NixOS is +// neither FHS nor ld.so.conf: hardware.graphics publishes the host's VA +// drivers under /run/opengl-driver/lib/dri, the directory sharun appends for +// the bundled runtime. A missed access() costs nothing. static const char *const cld_va_libdirs[] = { + "/run/opengl-driver/lib", "/usr/lib/" CLD_TRIPLET, "/lib/" CLD_TRIPLET, "/usr/lib64", "/lib64", "/usr/lib", "/lib", diff --git a/src/gl-fwd.c b/src/gl-fwd.c index 01574ac..d69753d 100644 --- a/src/gl-fwd.c +++ b/src/gl-fwd.c @@ -704,8 +704,15 @@ static const char *const glfwd_names[GLFWD_COUNT] = { * PR #4's mesa-egl entries are kept in list 3 for the same reason: a host with * the alternatives layout and no readable ld.so.conf is not hypothetical, and * an access() that misses costs nothing. + * + * NixOS is a host class none of the three reaches by itself: it is not FHS and + * it writes no /etc/ld.so.conf naming its GPU userspace, which lives under + * /run/opengl-driver/lib (with the system profile at + * /run/current-system/sw/lib). sharun names the same two for the bundled + * runtime, and an access() that misses costs nothing. */ static const char *const glfwd_host_dirs[] = { + "/run/opengl-driver/lib", "/run/current-system/sw/lib", "/usr/lib/" GLFWD_TRIPLET, "/lib/" GLFWD_TRIPLET, "/usr/lib/" GLFWD_TRIPLET "/mesa", "/usr/lib/" GLFWD_TRIPLET "/mesa-egl", "/usr/lib64", "/lib64", "/usr/lib64/mesa", "/usr/lib64/mesa-egl", diff --git a/src/runtime-select.c b/src/runtime-select.c index 7f1fe41..f872f76 100644 --- a/src/runtime-select.c +++ b/src/runtime-select.c @@ -113,6 +113,15 @@ static const char *rs_host_libdirs[] = { NULL }; +/* NixOS is not FHS and writes no /etc/ld.so.conf: the host's GPU userspace is + * published under /run/opengl-driver/lib and its system profile under + * /run/current-system/sw/lib. sharun appends the same two after everything + * else, and this is that act for the switched runtime. */ +static const char *rs_nixos_libdirs[] = { + "/run/opengl-driver/lib", "/run/current-system/sw/lib", + NULL +}; + static int rs_debug; static void rs_log(const char *fmt, ...) { @@ -1029,6 +1038,9 @@ static char *rs_library_path(const struct rs_plan *p, const char *appdir, /* The budget and its one-shot warning are per-walk state inside * ldconf_each_dir now, so there is nothing to reset here. */ rs_conf_dirs("/etc/ld.so.conf", s, cap, &n); + for (size_t i = 0; rs_nixos_libdirs[i]; i++) + if (!rs_path_append(s, cap, &n, rs_nixos_libdirs[i])) + rs_log("library-path is full; %s omitted\n", rs_nixos_libdirs[i]); return s; } From 335a3942639002728003db7c9c4e9fa54a2f2acc Mon Sep 17 00:00:00 2001 From: Nemo <328747105+Nemo-010@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:20:39 +0000 Subject: [PATCH 2/2] Move the totals to what the suite measured The NixOS cases ran in the container suite on both architectures and all three reported MATCH: 69/69 on x86-64 and 65/65 on aarch64. Section 8, the summary, section 9.7 and section 10 carried 66/66 and 62/62, and section 9.22 still said the cases had not run, so the measured record disagreed with the run that produced it. The one-home list in gates.yml and verify-gates.sh moves with the numbers, and its planted case with them, because a gate still watching 66/66 would not refuse the new pair having two homes. --- .github/workflows/gates.yml | 2 +- docs/report/01-summary.md | 2 +- docs/report/08-test-results.md | 26 ++++++++++++----------- docs/report/09-the-second-boundary.md | 25 +++++++++++++--------- docs/report/10-measured-versus-assumed.md | 2 +- scripts/verify-gates.sh | 4 ++-- 6 files changed, 34 insertions(+), 27 deletions(-) diff --git a/.github/workflows/gates.yml b/.github/workflows/gates.yml index beb58bd..3d5b03b 100644 --- a/.github/workflows/gates.yml +++ b/.github/workflows/gates.yml @@ -150,7 +150,7 @@ jobs: - name: every headline number has exactly one home run: | fail=0 - for n in 3470 358 66/66 62/62 45/45 40/40 26/26; do + for n in 3470 358 69/69 65/65 45/45 40/40 26/26; do homes=$(git grep -lF "$n" -- '*.md' ':(exclude)docs/history/*' | sed 's|^docs/report/.*|docs/report/|' | sort -u | tr '\n' ' ') count=$(printf '%s' "$homes" | wc -w) diff --git a/docs/report/01-summary.md b/docs/report/01-summary.md index b5d6c03..3fcb020 100644 --- a/docs/report/01-summary.md +++ b/docs/report/01-summary.md @@ -11,7 +11,7 @@ | Completion criterion | Status | |---|---| | Both goals demonstrated by a test that fails before and passes after | **Yes.** Goal 1: E5, E12. Goal 2: E22/E23 for the mechanism, E30/E32 and E37a/E37 for the end-to-end | -| The evidence harness still reports all predictions held | **Yes, 66/66** on x86-64 and **62/62** on aarch64, up from 22/22. The AppImage suite adds 45 on a glvnd glibc host, 40 on musl, 26 on each of two pre-glvnd glibc hosts and 7 on a real-application stage, with every unrunnable case SKIPPED by the capability it lacks | +| The evidence harness still reports all predictions held | **Yes, 69/69** on x86-64 and **65/65** on aarch64, up from 22/22. The AppImage suite adds 45 on a glvnd glibc host, 40 on musl, 26 on each of two pre-glvnd glibc hosts and 7 on a real-application stage, with every unrunnable case SKIPPED by the capability it lacks | | No host file modified, verified by checksum | **Yes.** T4.3, identical sha256 before and after | | Bundled libraries still win, verified via `dladdr` | **Yes.** T4.2, all resolved under `$APPDIR` | | A forward-compatibility story that does not depend on foresight | **Yes.** Host-runtime selection for the unenumerable gap, a generated shim for the enumerable one, and a build-time audit (E26) for the version traps | diff --git a/docs/report/08-test-results.md b/docs/report/08-test-results.md index 699269f..b9479c6 100644 --- a/docs/report/08-test-results.md +++ b/docs/report/08-test-results.md @@ -24,17 +24,19 @@ wrong. ### Tier 1, the evidence table -`sh scripts/run-evidence.sh` reports **66/66 predictions held on x86-64** and -**62/62 on aarch64**. The x86-64 total was measured at the change that added -E103; the aarch64 runner runs the same table, so its total is the x86-64 -total minus the four skips below, and CI re-runs both on every push. -`experiments/run.ps1` drives the same three stage scripts for a machine with -PowerShell and no POSIX shell. - -⚠ **E104, E105 and E106 are not in those totals yet.** They are the cases for -the NixOS host directories (section 9.22), they need root to create -`/run/opengl-driver/lib`, and the machine that added them had no root. The -next container run measures them and moves the totals to 69/69 and 65/65. +`sh scripts/run-evidence.sh` reports **69/69 predictions held on x86-64** and +**65/65 on aarch64**. The x86-64 total was measured at the change that added +E104, E105 and E106, the cases for the NixOS host directories, which the +container suite runs as root; the aarch64 runner runs the same table, so its +total is the x86-64 total minus the four skips below, and CI re-runs both on +every push. `experiments/run.ps1` drives the same three stage scripts for a +machine with PowerShell and no POSIX shell. + +⚠ **The three NixOS cases SKIP by name where `/run` is not writable.** E104, +E105 and E106 each create `/run/opengl-driver/lib`, which needs root, and each +reports the missing capability rather than being silently omitted. That is why +they were added to this table before they were measured: the machine that wrote +them had no root, and CI had it. ⚠ **The two totals differ by exactly the four cases aarch64 SKIPS**, each naming the capability it lacks rather than the difference being unexplained: @@ -48,7 +50,7 @@ naming the capability it lacks rather than the difference being unexplained: ⭐ **E23's skip is the one worth reading.** It was reporting MATCH on the ARM runner while asserting nothing, and skipping it with E22 is what stopped that. -66 minus 4 is 62, and no case is missing for a reason nobody wrote down. +69 minus 4 is 65, and no case is missing for a reason nobody wrote down. E1 through E13 measure the problem. E14 through E21 are one per fix from the first pass: the ELF self-test, the generated-shim compile and behaviour, and five selector diff --git a/docs/report/09-the-second-boundary.md b/docs/report/09-the-second-boundary.md index e25d533..2bd9ce4 100644 --- a/docs/report/09-the-second-boundary.md +++ b/docs/report/09-the-second-boundary.md @@ -362,7 +362,7 @@ Totals with this section in: **40/40 on the musl host** with five named skips, and ubuntu:16.04 with nineteen named skips, **7/7** on the gtk4 stage, and **53/53** in the container suite on x86-64, and **50/50** on aarch64 with the three skips named in section 8, at the measurement this section records; the -totals for the tree as it ships are **66/66** and **62/62** in section 8. +totals for the tree as it ships are **69/69** and **65/65** in section 8. ### 9.8 What the shim does not do, stated as a number @@ -1555,15 +1555,20 @@ to the library path after the `/etc/ld.so.conf` entries, and both join the shim's fixed host list. Every entry is access-guarded, so a host without them pays one failed probe each and nothing else. -E104, E105 and E106 are the cases. They need root to create -`/run/opengl-driver/lib`, which the machine that added them could not do, so -they are not in the 66/62 totals in section 8 until the container suite runs -them. The mechanism was measured on that machine with the prefix substituted -for a writable one: the assembled library path gains both directories after the -`/etc/ld.so.conf` ones, the shim reports `target -.../opengl-driver/lib/libtgt.so`, and the fake libva answers `DRIVER=NIX` from a -driver that exists only in the substituted directory. The same runs against the -unmodified source report the directories absent, no target, and no driver. +E104, E105 and E106 are the cases, one per list, and the container suite runs +all three on both architectures and reports MATCH for each, with +`DRIVER=NIX env=[/run/opengl-driver/lib/dri]` as E106's line. Section 8 owns the +totals. They create `/run/opengl-driver/lib`, so where `/run` is not writable +each SKIPS by name instead of running. + +The same three mechanisms were also measured before CI ran them, on a machine +with no root, with the prefix substituted for a writable one. There the +assembled library path gains both directories after the `/etc/ld.so.conf` +entries, the shim reports `target .../opengl-driver/lib/libtgt.so`, and the fake +libva answers `DRIVER=NIX` from a driver that exists only in the substituted +directory. The same runs against the unmodified source report the directories +absent, no target, and no driver, which is what makes each of the three an A/B +rather than a check that a string appeared. --- diff --git a/docs/report/10-measured-versus-assumed.md b/docs/report/10-measured-versus-assumed.md index 6352368..7dd167d 100644 --- a/docs/report/10-measured-versus-assumed.md +++ b/docs/report/10-measured-versus-assumed.md @@ -1,7 +1,7 @@ ## 10. Measured versus assumed **Measured:** every table and quoted output above, plus `sh scripts/run-evidence.sh` -(66/66 on x86-64, 62/62 on aarch64), `sh scripts/run-appimage.sh` (45/45 glvnd glibc, 40/40 musl with five +(69/69 on x86-64, 65/65 on aarch64), `sh scripts/run-appimage.sh` (45/45 glvnd glibc, 40/40 musl with five named skips, 26/26 on each pre-glvnd glibc host, 7/7 on the gtk4 stage), `tools/gap.py --fetch`, the eight-distro inventory, the AppImage inventory, the corpus test, the real mpv and `iHD` VA-API run in section 9.19, and the five-distro `ld.so.cache` survey in `../ground-truth.md`. diff --git a/scripts/verify-gates.sh b/scripts/verify-gates.sh index 68d8fad..5c8db62 100644 --- a/scripts/verify-gates.sh +++ b/scripts/verify-gates.sh @@ -93,7 +93,7 @@ g_attrib() { # section. Kept identical to the gates.yml step, including that. g_onehome() { f=0 - for n in 3470 358 66/66 62/62 45/45 40/40 26/26; do + for n in 3470 358 69/69 65/65 45/45 40/40 26/26; do c=$(git grep -lF "$n" -- '*.md' ':(exclude)docs/history/*' | sed 's|^docs/report/.*|docs/report/|' | sort -u | wc -l) [ "$c" -gt 1 ] && f=1 @@ -148,7 +148,7 @@ check "no tool is credited (tree)" g_attrib docs/_gate_probe.md \ check " the same, as a generated-with line" g_attrib docs/_gate_probe.md \ "Generated with [$TOOLNAME Code](https://example.invalid)" check "every headline number has one home" g_onehome docs/_gate_probe.md \ - 'the suite reports 66/66 predictions held' + 'the suite reports 69/69 predictions held' # ⛔ THIS CHECK EXISTS BECAUSE THE RATCHET DID NOT REFUSE. It was written as a # budget with a hardcoded number and a printed suggestion that the next reader