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/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/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 fb85a30..b9479c6 100644 --- a/docs/report/08-test-results.md +++ b/docs/report/08-test-results.md @@ -24,12 +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. +`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: @@ -43,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 @@ -76,6 +83,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..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 @@ -1531,6 +1531,45 @@ 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, 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/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/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 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; }