Repository navigation
fix: fall back to /usr/lib/os-release when /etc/os-release is absent - #675
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Non-absence read failures incorrectly trigger fallback, and the WSL distro probe and an existing host-dependent test remain inconsistent with the new behavior.
3 open findings
What changed in this PR
Adds /usr/lib/os-release fallback support to the shared distro-detection paths introduced by dependent PR #544.
Changes:
- Adds a shared fallback reader and integrates it with host probes and package planning.
- Adds unit and end-to-end fallback coverage.
- Updates testing, trust, and CI documentation.
| File | Description |
|---|---|
.github/workflows/ci.yml |
Documents fallback test coverage. |
apps/rocm/src/runtime_install_finalize.rs |
Uses the shared reader for package plans. |
crates/rocm-core/src/examine.rs |
Adds fallback-based examination and tests. |
crates/rocm-core/src/hardware_root.rs |
Implements the shared fallback helper. |
crates/rocm-core/src/host_gpu.rs |
Uses fallback for distro naming. |
crates/rocm-core/src/openmpi.rs |
Uses fallback for install hints. |
docs/release-trust.md |
Documents simulated-root behavior. |
docs/testing.md |
Documents fallback behavior and tests. |
tests/e2e-cucumber/features/examine.feature |
Adds the fallback scenario. |
tests/e2e-cucumber/tests/e2e/examine_steps.rs |
Plants fallback-only test data. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
os-release(5) says to read /etc/os-release and, if it does not exist, fall back to /usr/lib/os-release. Every reader in rocm-cli read only /etc/os-release, so a host that ships the file only at /usr/lib/os-release (minimal/container images, some distro variants) read as an unknown distro: `rocm examine` reported nothing and `rocm install driver` planned nothing. Add one shared helper (hardware_root::read_os_release) that tries /etc/os-release then /usr/lib/os-release, and wire it into the two functions that now do all the reading for the six call sites this fixes (examine's distro probe, host_gpu's distro name, openmpi's install-hint reader, and runtime_install_finalize's package-manager reader), preserving each site's existing host_path vs. real-host resolution strategy. Fixes #545. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
- read_os_release_ignores_a_simulated_host_root compared against a raw /etc/os-release read, which would fail on exactly the /usr/lib/os-release-only hosts this fix supports; compare through the same fallback helper instead. - hardware_root::read_os_release fell back on any read error, not just absence; a permission-denied /etc/os-release could silently adopt a stale /usr/lib/os-release. Narrow to ErrorKind::NotFound. - The WSL remote-distro shell probe in examine.rs sources /etc/os-release directly in guest-side sh, a reader this fix missed. Apply the same existence-based fallback there, factored into a function so its logic can run under a real /bin/sh against test-controlled paths. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
6046b84 to
838a521
Compare
tomastola
left a comment
There was a problem hiding this comment.
These findings were reviewed at commit 6046b8437e9e0c6bd78de823e4462b86887a30ac, which is no longer on this PR: the head has since moved to 838a521c (the branch was rebased or force-pushed, and the old commit is not fetchable). They are filed as a change request at the engineer's request, but they are unanchored and were not re-checked against the new head. Several may already be fixed or may no longer apply; in particular the new head sits on top of main's "one parser that matches sh (#544)" change, which I did not review. Verify each against the current code before acting.
Review — needs work (at 6046b84)
Full review of the whole change (base ee3131be … head 6046b84, PR #675, 15 files).
The PR implements the /usr/lib/os-release fallback. It also swaps in a new strict os-release parser, which is a larger change than the title says. No ticket is named. The PR description was not read (see What this covered).
Blocking
-
Parse errors name
/etc/os-releaseeven when/usr/lib/os-releasewas read.crates/rocm-core/src/examine.rs:763,crates/rocm-core/src/host_gpu.rs:~1915,crates/rocm-core/src/openmpi.rs:844and~899,apps/rocm/src/driver_install.rs:853.
hardware_root::read_os_releasereturns onlyOption<String>(hardware_root.rs:130), so the path it read is lost. On a machine that has only the fallback file, which is the case this PR exists for, a bad line tells the user to fix a file that does not exist. The new tests pin that wording as correct:an_unreadable_os_release_names_its_line_and_plans_nothing(driver_install.rs) andan_unreadable_file_plans_nothing_and_names_its_line(openmpi.rs). No test covers a malformed fallback file.
Confidence 85 · logic · Fix: return the path read alongside the text, and have every message use it. -
An unreadable
/etc/os-releaseis still reported as an unsupported distro.apps/rocm/src/driver_install.rs:38,apps/rocm/src/runtime_install_finalize.rs:207and:382,openmpi.rsreal_os_release(),examine.rsrecord_distro(~756),host_gpu.rsdetect_distro_name(~1888).
read_os_releasereturnsNonewhen/etc/os-releaseexists but cannot be read (permission denied, a directory, invalid UTF-8) and when neither file exists. Every caller turns that into empty text withunwrap_or_default(), and empty text parses asOk(empty). The driver plan then says "no commands were guessed for this distro". The openmpi, libatomic and libnuma plans say "Could not map distribution (ID=)".examinereports an empty distro with no entry inprobe_failures.detect_distro_namereturnsNoneanddistroprints<unknown>with no reason.
The PR adds line-level diagnostics for a bad line but not for a file that cannot be read, which is the same class of fault it set out to close. TheResultreturned byruntime_install_finalize::read_os_releaseis dead, because all three callers discard the error.
Confidence 80 · logic · Fix: carry the read error to the reporting sites. -
Nothing pins the fallback at the install-plan call sites.
crates/rocm-core/src/openmpi.rs(real_os_release()and the three*_install_hint()functions),apps/rocm/src/runtime_install_finalize.rs:2094-2130.- Revert check, verdict 1:
install_hint_is_non_empty_and_actionablepasses with the old/etc-only read, because the manual hint also contains "install". read_os_release_ignores_a_simulated_host_root: it comparesread_os_release()withhardware_root::read_os_release(...), which is the same function, so for the fallback it asserts nothing. It also only compiles undere2e-test-hooks.- Reverting only
runtime_install_finalize::read_os_releaseto read/etc/os-release: the apps tests still pass on any host that has/etc/os-release. - Where the fallback is tested: only the
hardware_rootunit tests and theexaminepath (examine-24andos_release_reads_fall_back_to_usr_lib_when_etc_is_absent) exercise it. - Bypass of
host_path: no test checks thatopenmpi.rs's ownreal_os_release()bypasses it.
Confidence 70 · logic.
- Revert check, verdict 1:
-
Dangling doc reference to
rocm_core::detect_distro_name.apps/rocm/src/runtime_install_finalize.rs:158.
The function ispub(crate)inhost_gpu.rs:1888and is not re-exported inlib.rs, so the path in the comment does not resolve.
Confidence 85 · mechanical · Fix: reference the real call path, or drop the path. -
The new test
try_os_release_sources_the_fallback_only_when_the_primary_is_absentclaims a branch it cannot fail on.crates/rocm-core/src/examine.rs:~5750and theTRY_OS_RELEASE_FNdoc.
The doc says a permission-denied primary is not replaced by the fallback, but the test covers only an absent primary and a present one. Changing-eto-rin the shell function would still pass.
Confidence 65 · mechanical · Fix: add an unreadable-primary case, or drop the claim. -
The
E2E testscheck was red. CI run 38545719607, job 115793891572.
The failing scenario ischat-06(chat tool definitions). Its step timed out onGET /v1/models, 18.8 s and 10.5 s against a 10 s budget.serve-01andserve-02failed as expected xfails. All five newexamine-20toexamine-24scenarios passed. The chat path is untouched and does not read os-release, so this is probably a flake, though I did not see a passing run on the base commit. A failing check blocks regardless until it is re-run green. (main has since landed a chat-06 stall fix, #662, which the new head includes.)
Confidence 80 that it is unrelated · Fix: re-run the job.
Non-blocking
-
A single bad line now voids every field.
crates/rocm-core/src/os_release.rsparse, with consumers inexamine.rs:~731,host_gpu.rs:~1895,driver_install.rs:~849andopenmpi.rs.
The old readers tookID,VERSION_IDand the name line by line and skipped what they did not understand. The new parse is whole-file and fail-closed. Files that used to work and now fail: CRLF files, files with a BOM, an unrelated unquoted value such asANSI_COLOR=0;31, and a\r-only blank line. The module documents this as deliberate, and I found no parser defect againstos-release(5). The cost lands on hand-edited or Windows-edited files in WSL. The PR title does not say it widens the behaviour change.
Confidence 55 · architectural. -
Dead code.
crates/rocm-core/src/openmpi.rs:882,impl Display for InstallHint: no caller formats anInstallHintwith{}.
TheErrarm ofplan_for_os_releaseis unreachable from the hint path, becauseinstall_hint_forchecks and returns first. The file is parsed twice for each hint.
Confidence 60 · mechanical. -
docs/testing.mdis garbled and overclaims. The paragraph "The two reads this covers areos_release.rs's own…" is wrong, becauseos_release.rsdoes no file reads. The two reads are theexamine.rsandhost_gpu.rscall sites. The same section says every reader goes throughhardware_root::read_os_release, which is not true of the WSL remote probe, which has its own shell copy.
Confidence 65 · mechanical.
Decisions for the author
- Whole-file strict parse (fail-closed) versus the old tolerant per-line read — tradeoff. Fail-closed means a malformed file is refused outright, never planned from a half-read line, and the one
rocm_core::os_releaseparser keepsexamineand the driver plan in agreement. The cost is thatexaminegoes degraded and the install plans refuse on files that used to work (see the first non-blocking item). Confirm this is intended, and consider saying so in the title and description. - Fall back only on
NotFound— tradeoff. This matches the-eshell check, and a permission-denied primary is not silently replaced by a possibly stale fallback. The price is the unreadable-file silence in blocking finding 2. Confirm that is deliberate.
Positive signals
- Both fallback readers route through
hardware_root, so a simulated host root cannot leak to the real host. Both candidate paths resolve under the root. - The
examine-24scenario andos_release_reads_fall_back_to_usr_lib_when_etc_is_absentboth fail if the fallback is reverted. I traced this by reading the code; I did not run it. - Checking
-erather than readability in the WSL shell probe is a sound choice and is documented.
Deployment notes
None. There is no migration or new dependency. The user-visible changes are the new unreadable_os_release plan policy and the probe_failures text.
What this covered
I read the full diff and the full text of os_release.rs, hardware_root.rs, openmpi.rs, the e2e files, the docs and the CI hunks. I read the changed hunks and surrounding code of examine.rs, host_gpu.rs, driver_install.rs and runtime_install_finalize.rs; I did not read those four files end to end. The review was split across three workers by coupling. Nothing was built or run, so no finding rests on a run. Review base: ee3131be…6046b84.
Did not run: the history and prior-changes passes. Step 9 (description and ticket) was not done: I did not read the PR description, and no ticket is named.
Not reconciled against any discussion here.
juhovainio
left a comment
There was a problem hiding this comment.
Reviewed this (closes #545) with a fan-out correctness pass plus a separate Standards/Spec pass. The core fix is solid: one shared helper (hardware_root::read_os_release) that falls back from /etc/os-release to /usr/lib/os-release only on NotFound (never masking a real permission error with a stale fallback), wired into every one of the six original call sites plus a 7th (a WSL shell probe) the author's own second commit caught and fixed. CI is fully green, tests exist at all three levels, and the two-commit shape (fix, then review-feedback response) is honest about what changed when.
Requesting changes on one real test-quality gap and a couple of now-stale error strings, left as inline comments below - none of these affect runtime behavior, but a test that can't fail and an error message that now says less than it knows both deserve a quick pass before merge:
- The
assert_eq!inruntime_install_finalize.rs's simulated-root test now compares the function under test's output against the exact same helper call it's built from (samehardware_root::read_os_release, same identity resolver) - it'll pass even if the wrapper's.context(...)or path logic breaks, since it's no longer checking against an independent source of truth. examine.rs::record_distro's andhost_gpu.rs::distro_name_from_os_release's unreadable/malformed-line error messages still hardcode "/etc/os-release" - now potentially wrong, since the text being reported on could have come from the fallback file.- Two small
docs/testing.mdinaccuracies: it attributes the probe-routing logic toos_release.rs(that module is just the shared parser; the actual host-root-aware reads live inexamine.rs/host_gpu.rs), and it describeshardware_root::tests::read_os_release*as covering 3 cases (primary found, primary absent, neither found) when a 4th test (read_os_release_does_not_fall_back_when_the_primary_errors_for_another_reason) exists and isn't mentioned.
A few more things worth a look, not blocking: a naming collision between two different functions both effectively called read_os_release (the raw helper vs. the package-manager-plan wrapper) - no actual bug, just a readability nit; whether the four near-identical WSL/host-path resolver closures (|p| host_path(p) etc.) are worth factoring into one helper; and a couple of untouched advisory strings elsewhere that are now very slightly stale but out of this PR's stated scope.
None of this undermines the fix itself - closing the three error-text/test-quality items would make this good to merge.
| assert_eq!( | ||
| read, | ||
| fs::read_to_string("/etc/os-release").unwrap_or_default() | ||
| rocm_core::hardware_root::read_os_release(|p| PathBuf::from(p)).unwrap_or_default() |
There was a problem hiding this comment.
This now compares read (the output of read_os_release(), the function under test) against rocm_core::hardware_root::read_os_release(|p| PathBuf::from(p)).unwrap_or_default() - which is exactly the same helper, called with the same identity resolver, that read_os_release() itself wraps one line away (apps/rocm/src/runtime_install_finalize.rs:160-161). The comment above explains why the old comparison (fs::read_to_string("/etc/os-release")) was wrong on a fallback-only host, which is correct - but the replacement isn't an independent check either, it's the implementation comparing itself to itself. If the wrapper's .context(...) call or its resolver closure ever introduced a bug, this assertion would still pass. Worth comparing against something that doesn't share the same code path - e.g. reading both candidate paths directly under the simulated root and asserting the result matches whichever one actually exists.
There was a problem hiding this comment.
Confirmed, and fixed further: the follow-up commit 2dcb8314 ("fix: make the simulated-host-root test compare against an independent read") stopped comparing against hardware_root::read_os_release a second time, but still accepted Absent/Unreadable interchangeably. 5c78fdbf ("fix: assert the exact OsRelease variant in the simulated-root test") tightens that further: the expected value is now built as the same three-way OsRelease the reader returns (via an ExpectedOsRelease enum mirroring hardware_root::read_os_release's own branches), so the test fails if the real read ever blurs Absent and Unreadable.
| record_distro(e, &read_host_text("/etc/os-release")); | ||
| record_distro( | ||
| e, | ||
| &crate::hardware_root::read_os_release(|p| crate::host_path(p)).unwrap_or_default(), |
There was a problem hiding this comment.
Not on this line directly, but record_distro's unreadable-text branch a little further down (examine.rs:763, unchanged by this diff) still reports "/etc/os-release {unreadable}; the distro is not reported." hardcoded. Now that this call can hand record_distro text that actually came from /usr/lib/os-release, that message can name the wrong file to an operator debugging a malformed-line case on a fallback-only host. Same issue applies to host_gpu.rs:1992's format!("Linux (/etc/os-release {unreadable})"). Worth having hardware_root::read_os_release (or its callers) track which candidate was actually read, so these messages can name it.
There was a problem hiding this comment.
Fixed in b6b0abdf ("fix: non-blocking doc/comment nits and the last two /etc-only readers") and earlier commits in this chain: record_distro's message at examine.rs:761 now interpolates {path} from the OsRelease::Unreadable variant rather than a hardcoded string, so it names whichever candidate (/etc/os-release or /usr/lib/os-release) was actually read.
| if runtime_is_linux() { | ||
| return Some(distro_name_from_os_release( | ||
| &fs::read_to_string(host_path("/etc/os-release")).ok()?, | ||
| &hardware_root::read_os_release(|p| host_path(p))?, |
There was a problem hiding this comment.
Same stale-error-text issue as the one left on examine.rs:733: distro_name_from_os_release's unreadable branch at host_gpu.rs:1992 (unchanged by this diff) still says "Linux (/etc/os-release {unreadable})" unconditionally, even though the text it's formatting an error about may have come from /usr/lib/os-release via this new call.
There was a problem hiding this comment.
Fixed: distro_name_from_os_release at host_gpu.rs:1994 now interpolates {path} from the OsRelease::Unreadable variant instead of a hardcoded /etc/os-release, so Linux (<path> could not be read: ...) names the candidate actually read.
| `/usr/lib/os-release` per `os-release(5)` — a file present but unreadable (the | ||
| line-reporting cases below) does not fall back, since the fix there is to | ||
| correct the line, not to read a second file. The two reads this covers are | ||
| `os_release.rs`'s own: a probe routed through a simulated test root |
There was a problem hiding this comment.
This says "The two reads this covers are os_release.rs's own" - but os_release.rs (per its own module doc) is "the one parser every reader of the file uses," not a reader itself. The two reads that route through a simulated test root live in examine.rs (probe_os/record_distro) and host_gpu.rs (detect_distro_name), both calling hardware_root::read_os_release with host_path as the resolver. Worth correcting the attribution so a reader looking for the probe logic doesn't go hunting in the wrong file.
There was a problem hiding this comment.
Fixed in b6b0abdf: the paragraph now attributes routing to hardware_root::read_os_release (hardware_root.rs), not os_release.rs, and describes the two actual reads as the host_path-routed probe (examine/host_gpu) versus the real-machine package-manager plan (openmpi/driver_install/runtime_install_finalize) — see docs/testing.md's /etc/os-release section.
| needs is enabled only by its own CI step. | ||
| The parser tests that compare against `/bin/sh` itself are Linux-only. The | ||
| `hardware_root::tests::read_os_release*` tests check the fallback itself | ||
| (primary found, primary absent, neither found) against an injected temp |
There was a problem hiding this comment.
This lists three cases for hardware_root::tests::read_os_release* ("primary found, primary absent, neither found"), but there are four tests in hardware_root.rs: those three plus read_os_release_does_not_fall_back_when_the_primary_errors_for_another_reason, which is arguably the most important one to document since it's what keeps a permission-denied primary from silently reading a stale fallback. Worth adding it to the list.
There was a problem hiding this comment.
Fixed in b6b0abdf: the sentence now lists all four hardware_root::tests::read_os_release* cases (primary found, primary absent, neither found, and each candidate existing but erroring for a reason other than absence), naming read_os_release_does_not_fall_back_when_the_primary_errors_for_another_reason explicitly.
…release Every message that reports an unreadable os-release line hardcoded "/etc/os-release", even when the text came from the /usr/lib/os-release fallback this PR adds. Fix that by threading which candidate was actually read through hardware_root::read_os_release's new OsRelease return type (Found/Absent/Unreadable) into every message-building call site: the driver install plan, the OpenMPI/libatomic/libnuma install plans and hints, and rocm examine's distro fields and distro name. The same type change closes a second gap: a file that exists but cannot be read (permission denied, a directory in its place) was previously indistinguishable from an unknown distribution by the time it reached these call sites, since every caller discarded the read error with unwrap_or_default(). It is now reported as itself. Also: - add a test exercising the WSL remote probe's try_os_release shell function against a primary that exists but errors, mirroring the existing non-absence case on the Rust side - add tests pinning that a fallback-sourced read plans and hints identically to a primary-sourced read with the same text, at the one seam the real call sites (which must always read the real machine and so can't honour a simulated root) hand their result to - fix a dangling doc reference to rocm_core::detect_distro_name, which is pub(crate) and unreachable from apps/rocm - fix two testing.md wording bugs: a misattribution of two file reads to os_release.rs, which does no file reads, and a blanket "every reader" claim contradicting the WSL-probe exception stated two sentences earlier Signed-off-by: Jussi Elo <jussi.elo@amd.com>
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 5f2c3ff
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
First automated round on this PR: every finding below is new.
Review — needs work
Full review of the whole change.
Implements #545 (open): the change routes every in-process reader through one helper and gives the WSL remote probe its own shell copy of the fallback. The description says "Fixes #545", and that holds.
Blocking
-
The
prek (lint / hygiene)CI check fails oncargo fmt, so the test and Windows jobs never ran for this head —apps/rocm/src/driver_install.rs:38,:817,crates/rocm-core/src/examine.rs:3180,crates/rocm-core/src/openmpi.rs:1030,:1337,:1350cargo fmt --all -- --checkreproduces it locally, and every hunk is in lines this PR adds.- Because prek failed, CI skipped "build-and-test", "Test (affected crates)" and "windows-build-and-test". This head has no CI unit-test run and no Windows build.
- I could not cover Windows locally either: the cross-check for
x86_64-pc-windows-gnufails becauseringneedsx86_64-w64-mingw32-gcc, which is not installed here.
Confidence 100 · mechanical · Fix: run
cargo fmt --all, push, and let the skipped jobs run. -
The two behaviours the newest commit and the new seams add are not tested: naming the file actually read, and the WSL/Windows guard —
crates/rocm-core/src/openmpi.rs:894,:1001,crates/rocm-core/src/examine.rs:772,crates/rocm-core/src/host_gpu.rs:2002,apps/rocm/src/driver_install.rs:1013-1014,:1036- Commit 5f2c3ff ("name the file a fallback read actually read") exists to change these messages. I replaced
read.path()with"/etc/os-release"at all five sites. The full rocm-core lib suite with--features e2e-test-hooksstill passed (676 tests), and so didcargo test -p rocm --bin rocm -- driver_install os_release(59 tests). - No test has a
Found { path: "/usr/lib/os-release" }value whose text fails to parse. Botha_fallback_read_plans*tests use validID=ubuntutext, so the path never shows up in the output. - In the same run I also replaced the
reaches_os_release_parseguard withtrue, and it stayed green. On WSL with an unreadable file, that mutation turns the ROCDXG plan intounreadable_os_release. - The test comments at
openmpi.rs:1409-1413anddriver_install.rs:1943-1946say a revert "is a type error here". It is not:OsRelease::Found { path: "/etc/os-release", text: read_to_string(..).unwrap_or_default() }compiles.
Confidence 100 · logic · Fix: add tests with
Found { path: "/usr/lib/os-release", text: "unset ID\n" }that assert the path in the plan reason, the hint, the driver reason,record_distro's probe failure and the distro name. Add a WSLtest_examine+Unreadablecase that asserts the WSL policy. Correct the "type error" comments. - Commit 5f2c3ff ("name the file a fallback read actually read") exists to change these messages. I replaced
-
In examine-24, the step "the inspection reports no trouble reading the release file" can never fail —
tests/e2e-cucumber/tests/e2e/examine_steps.rs:1401-1410,:1431-1435os_release_failureskeeps only entries that contain"/etc/os-release".- examine-24 plants only
/usr/lib/os-release, and since this PR every failure message names that file. A read or parse failure there is filtered out before the assertion sees it. - The scenario still catches a revert, through its distribution assertions. This step is the only vacuous one.
Confidence 85 · logic · Fix: match on
"os-release", or on both paths. -
The new handling of a file that exists but cannot be read changes what users see and has no scenario (AGENTS.md §3) —
crates/rocm-core/src/examine.rs:759-764,crates/rocm-core/src/host_gpu.rs:1971-1978,:1990-1992- Before this PR, an unreadable but present
/etc/os-releasewent throughread_host_textand came back empty. That produced no probe failure, no degraded verdict, andsummary.distrowasNone. - Now
rocm examine --jsongets a newprobe_failuresentry, the status becomesdegraded, and the printed distro readsLinux (<path> could not be read: …). The driver and OpenMPI/libatomic/libnuma plans also get a new reason. - Only unit tests with a hand-built
Unreadablevalue cover this. - A scenario is easy to plant: put a directory at
host-root/etc/os-release(a non-NotFounderror that needs no root) and reuse examine-23's GPU planting.
Confidence 85 · logic · Fix: add a Gherkin scenario that asserts the degraded verdict, the failure text and the
distroline together. - Before this PR, an unreadable but present
-
The test for "the primary exists but errors" in the WSL probe never hits an error, and a real unreadable primary aborts the whole probe —
crates/rocm-core/src/examine.rs:860-866, the testtry_os_release_does_not_source_the_fallback_when_the_primary_exists_but_errors(~:5844),docs/testing.md(the "same non-absence case" sentence)- The probe runs as
wsl.exe --exec /bin/sh -c …, and/bin/shis dash on Debian and Ubuntu. - Under dash, the test's directory fixture is sourced silently with rc 0 and prints
id=. The test therefore only tells-eapart from-f, not "errors". - A genuinely unreadable primary (mode 000, as uid 1000) makes dash abort
try_os_releasewith rc 2 and print nothing. The caller at:953then reports "could not inspect ''; the distribution may be stopped or unreachable" and throws away every other probe line. - The abort on an unreadable primary existed before this PR. What is new is the test and the docs, which claim this matches the Rust readers. The Rust readers report
Unreadable; this probe fails entirely.
Confidence 100 (behaviour reproduced with
dash -c) · logic · Fix: test with an unreadable file, not a directory (and note that CI running as root bypasses mode bits). Either make the shell guard survive it, for example[ -r "$1" ]inside the-ebranch plus anos_release_unreadable=marker, or narrow the doc claims. - The probe runs as
-
The description understates what the change does — PR body
- It says the change wires in the fallback while "preserving each one's existing resolution strategy and error handling exactly". The diff changes the error handling: it adds the
Found/Absent/Unreadablestates, a new degraded verdict and distro text inexamine, and new plan and hint reasons. - It does not mention the rewrite of
WSL_REMOTE_PROBE, the path-naming change, or the new unreadable-file tests. - It says "keeping only the two commits"; the branch has three.
Confidence 85 · mechanical · Fix: update the description to cover the work. The fix here is the description, not narrowing the code.
- It says the change wires in the fallback while "preserving each one's existing resolution strategy and error handling exactly". The diff changes the error handling: it adds the
Non-blocking
docs/testing.mdgets the delegation wrong (the new paragraph near "The three_read-suffixed functions"): it says the_readfunctions delegate to the bare-text functions. They delegate to the_atfunctions; the bare-text functions are siblings that also call_at. The code is right and the doc should change. Confidence 75 · mechanical.- The doc comments on
plan_for_os_release_readoverstate what the message avoids —crates/rocm-core/src/openmpi.rs:874-879,:886-891.- The comment says the change avoids the "Could not map distribution (ID=)" message.
- The reason still appends
build("", "").reason, which is that exact message, after the real cause. - The test only checks
starts_with, so per AGENTS.md §3 the message is not asserted together with the behaviour. - Either drop the appended reason or reword the comment. Confidence 70 · mechanical.
- Comments that are now stale — Confidence 70 · mechanical.
openmpi.rs:901: theInstallHintdoc namesplan_for_os_release, but production now callsplan_for_os_release_read.openmpi.rs:1024,:1047,:1073: "On Linux it reads/etc/os-release" does not mention the fallback.host_gpu.rs:4838: a test doc says "unchanged by this PR". That is wrong forUnreadable(NonebecameSome("Linux (…)")) and will be meaningless once merged.
- Two
verify:strings still read only/etc/os-release—crates/rocm-core/src/fix.rs:749,crates/rocm-core/src/diagnose.rs:2309(grep VERSION_ID /etc/os-release). These are the only os-release readers in the tree without the fallback. They predate this PR and only print on WSL, where Ubuntu ships the file, so the risk is low. Confidence 70 · mechanical.
Decisions for the author
- Three entry points per builder (bare /
_at/_read) — non-blocking-improvementbuild_driver_install_planandinstall_hint_forbecame#[cfg(test)], butopenmpi::plan_for_os_releasestays a non-testpub fnwith no production caller.- Tests could build
OsRelease::found(...)and call the_readseam directly. That would remove the bare variants and the always-/etcpath they keep alive.
- A duplicated guard and plan literal in
build_driver_install_plan_read(driver_install.rs:1013-1036) — tradeoff- Today the guard and the plan literal match
build_driver_install_plan_at's early returns and its unreadable-line plan field for field, apart from the reason. - Keeping them separate keeps the file-level case out of
_at's signature. The cost is that a new early branch added to_atwould silently be pre-empted.
- Today the guard and the plan literal match
Positive signals
hardware_root::read_os_releasefalls back only onNotFound. A file that exists but is unreadable is reported rather than quietly replaced by a possibly stale/usr/lib/os-release, and each branch has its own resolver-injected unit test.try_os_release_fn_is_embedded_in_the_wsl_remote_probestops the tested shell constant from drifting away from the probe that embeds it.
Deployment notes
- When
/etc/os-release(or the fallback) exists but cannot be read,rocm examine --jsonnow reportsstatus: degradedand a newprobe_failuresentry, where it used to report a silently empty distro. Anyone consuming the JSON verdict will see the change.
What this covered
I read every file in the change (11 files), plus the os-release readers across the whole tree, the WSL_REMOTE_PROBE caller, and the cucumber step helpers. Range: 21be003…5f2c3ffc.
- The code passes ran as two independent read-only workers, one on the examine/hardware_root/host_gpu/e2e group and one on the openmpi/driver_install/runtime_install_finalize/docs group. I did the consolidation, the verification runs and the mutations.
- Evidence runs:
- The fmt check.
cargo test -p rocm-core --lib --features e2e-test-hooksfiltered: 46 os-release tests passed, including every new one.- Mutations: the full rocm-core lib (676 passed) and
-p rocm --bin rocm -- driver_install os_release(59 passed). - dash probes of
try_os_release.
Did not run:
- The Step 7 design questions ran in the driver context, not as independent workers (subagent budget).
- The prior-changes pass: earlier review comments are out of bounds for this run.
- The Windows-target check: mingw is not installed.
- The full workspace tests and cucumber e2e: I relied on CI's passing E2E job for examine-24.
Not reconciled against any PR discussion here.
Signed-off-by: Jussi Elo <jussi.elo@amd.com>
It was the one non-test pub fn among the bare-text os-release helpers with no production caller - every real call site already goes through plan_for_os_release_at/_read. Migrate its 4 test call sites to plan_for_os_release_read with OsRelease::found(text) and drop it, along with the doc references that named it. install_hint_for and build_driver_install_plan stay as bare-text test helpers: they're load-bearing for dozens of unrelated test scenarios, not redundant plumbing, so collapsing them is left for a follow-up. Signed-off-by: Jussi Elo <jussi.elo@amd.com> (cherry picked from commit f88888f70d81e5a3534d26715aa05894cd43c149)
… read read_os_release_ignores_a_simulated_host_root's second assertion compared the function under test against a second call to the exact same hardware_root::read_os_release helper with the same identity resolver - not independent, so a bug in the wrapper's .context(...) call or resolver closure would pass unnoticed. Replace it with a direct read of both candidate paths, applying the same existence-only fallback rule inline; where neither candidate is cleanly readable on this box, assert only the Absent/Unreadable shape rather than exact text. Also rename runtime_install_finalize::read_os_release to real_os_release, matching openmpi.rs's existing name for the analogous real-host-only reader and resolving the naming collision with the hardware_root helper it wraps. Signed-off-by: Jussi Elo <jussi.elo@amd.com> (cherry picked from commit a247bc6e4897ed64dc1b98683f5c07c32f9f777f)
rominf's mutation testing found that replacing read.path() with a
hardcoded /etc/os-release at the five message-building call sites still
passed every existing test: every malformed-line test went through either
the bare /etc-only functions or OsRelease::found (which also hardcodes
/etc/os-release), never a Found{path: "/usr/lib/os-release", ...} fed to
the path-aware _read functions.
Add one test per site (openmpi.rs's plan_for_os_release_read/
install_hint_for_read, driver_install.rs's build_driver_install_plan_read,
examine.rs's record_distro, host_gpu.rs's distro_name_from_os_release)
that would have caught it.
Also add a_wsl_host_ignores_an_unreadable_local_os_release_read: on WSL,
build_driver_install_plan_read's Unreadable branch must not fire at all,
since WSL has its own guest-side probe and the local Rust-side read is
never authoritative for it - nothing previously pinned that the
reaches_os_release_parse guard keeps this true.
Signed-off-by: Jussi Elo <jussi.elo@amd.com>
(cherry picked from commit 94eddfcca97f4cb0a428777722276df31ca7cbc3)
os_release_failures()'s filter hardcoded "/etc/os-release", so a release-file failure on a fallback-only host (every message now names the file actually read) was silently dropped before examine-24's "reports no trouble reading the release file" step ever saw it - the step could not fail. Broaden the filter to match either candidate path. Add examine-25: before this change, a release file that exists but cannot be read silently produced an empty, unreported distro with no failure recorded; it now reports the file as itself and degrades the verdict, which had no e2e coverage. Factor the GPU-fixture planting shared with examine-23 into plant_gpu_fixtures to avoid duplicating it. Signed-off-by: Jussi Elo <jussi.elo@amd.com> (cherry picked from commit 093b970648585777f03f5bddc1d083fb87344e0d)
…ders try_os_release_does_not_source_the_fallback_when_the_primary_exists_but_errors used a directory as the primary (deliberately: CI commonly runs as root, where chmod 000 is ignored, so a mode-000 fixture would be flaky depending on who runs it) but its name and docs/testing.md both claimed parity with the Rust-side hardware_root tests' permission-denied case. They are not the same failure mode: under dash, sourcing a genuinely unreadable file aborts the whole embedded script immediately, not just this function, while a directory fails to source for a different, root-safe reason. Rename to try_os_release_does_not_source_the_fallback_when_the_primary_is_not_sourceable and tighten both doc comments to state what the test actually proves (existence is not readability) and name the gap it leaves, rather than claiming parity the fixture can't back up. The underlying abort-on- unreadable-primary behavior predates this PR; fixing it is out of scope here. Signed-off-by: Jussi Elo <jussi.elo@amd.com> (cherry picked from commit b069a8df514961c642ee2c31f287937e684ce448)
Replace the four near-identical `|p| host_path(p)`/`|p| PathBuf::from(p)` resolver closures passed to hardware_root::read_os_release with two named functions (host_path_for_str, identity_path_for_str) in hardware_root.rs. A bare reference to host_path or PathBuf::from doesn't work in their place - the borrow checker rejects the generic fn as "not general enough" over the resolved lifetime - so this needed a concrete fn(&str) -> PathBuf wrapper, not just dropping the closure. Also extract build_driver_install_plan_at's and _read's identical unreadable-os-release DriverInstallPlan literal (same field-for-field shape, differing only in the reason string) into a shared unreadable_os_release_plan helper, so a new early branch added to one can't silently fail to apply to the other. Signed-off-by: Jussi Elo <jussi.elo@amd.com> (cherry picked from commit cbe194a206cf59da6f4a052dea1295126e8d0d20)
- docs/testing.md's "_read-suffixed functions" paragraph had the delegation backwards: they delegate to the _at functions, not to the bare-text ones (which are _at's siblings, not something _read routes through). Also drops plan_for_os_release from the list now that it's deleted, and bumps the examine-20..24 ranges to ..25. - plan_for_os_release_read's doc comment claimed the fix avoids the generic "Could not map distribution (ID=)" message; it still appends it, just with the real cause prepended. Reworded to say what actually happens. - Three install_hint doc comments (openmpi/libatomic/libnuma) said "reads /etc/os-release" without mentioning the fallback. - A test doc in host_gpu.rs said the unreadable-file case was "unchanged by this PR" when it's the opposite - new behavior - and referencing a PR goes stale the moment it merges. fix.rs and diagnose.rs's "fix-wsl-5-distro-too-old" verify: strings were the only os-release readers left in the tree without the fallback - both now check /usr/lib/os-release too. Signed-off-by: Jussi Elo <jussi.elo@amd.com> (cherry picked from commit e072ebee210bd55615e2e17e8b962898eba18ff0)
Both call sites resolved the same default template before calling the helper, which only ever discards it (the unreadable plan has no commands). Move the resolution inside so a future default bump can't update one call site and miss the other. Signed-off-by: Jussi Elo <jussi.elo@amd.com> (cherry picked from commit db1db884f9934c153cc0a84763859b93d953941c)
real_os_release_ignores_a_simulated_host_root compared the real-host read against an independent fs::read_to_string fallback, but collapsed both "neither file exists" and "a candidate exists but can't be read" into one None/Absent-or-Unreadable branch - exactly the distinction hardware_root::read_os_release exists to preserve. Build the expected value as the same three-way OsRelease the reader returns instead, so the test fails if the real read ever blurs Absent and Unreadable. Signed-off-by: Jussi Elo <jussi.elo@amd.com> (cherry picked from commit 66e12fe986dfbb3f6eee9f47c7948ab5489432bd)
grep across two files prints a "filename:" prefix on every match unless told not to, so adding /usr/lib/os-release as a second grep argument made the verify line's output start with "/etc/os-release:" or "/usr/lib/os-release:" ahead of VERSION_ID - cosmetic noise in output meant to be copy-pasted as-is. -h suppresses it on both candidate paths. Signed-off-by: Jussi Elo <jussi.elo@amd.com> (cherry picked from commit f8bfa61097918786e78a3406a96bb23fbfced8e1)
"{path} could not be read: {error}" was hand-duplicated at all five
message-building call sites (the driver plan, the OpenMPI plan and
hint, examine's probe failure, and the distro name), with only the
trailing clause legitimately differing per site. Factor the shared
prefix into hardware_root::unreadable_prefix so a future wording change
to it can't be applied to four sites and missed on the fifth.
Signed-off-by: Jussi Elo <jussi.elo@amd.com>
install_hint_for_at's only non-test caller is install_hint_for_read, which already carries #[cfg_attr(not(target_os = "linux"), allow(dead_code))] since it has no caller at all off Linux. Give install_hint_for_at the same attribute for the same reason, instead of relying on it staying reachable through a function that is itself only conditionally alive. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
grep /etc/os-release /usr/lib/os-release falls back to the second file whenever the first merely fails to match VERSION_ID (no such line, permission denied), not only when it is absent - diverging from hardware_root::read_os_release's existence-only fallback rule this PR establishes. A host where /etc/os-release exists but lacks a VERSION_ID line would silently show /usr/lib/os-release's version instead. Pick the file by existence first, then grep only that one. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
a_wsl_host_ignores_an_unreadable_local_os_release_read only pinned the WSL half of the guard that keeps build_driver_install_plan_read's Unreadable branch from firing on a host the Linux driver install was never attempted on. Add the Windows counterpart: an Unreadable local read must still produce windows_validate_only, not unreadable_os_release. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
|
Status update at tomastola's review (filed against a stale head, explicitly flagged as unanchored)Re-checked each item against the current code rather than trusting the flag:
rominf's automated review (pr-review-watcher, at
|
…b-fallback Signed-off-by: Jussi Elo <jussi.elo@amd.com> # Conflicts: # tests/e2e-cucumber/features/examine.feature
juhovainio
left a comment
There was a problem hiding this comment.
Re-reviewing after the head moved past my earlier CHANGES_REQUESTED (commit 838a521) - this covers the current state fresh, via gh pr diff (no checkout).
The commits pushed since my original review read as a genuine continuation of that feedback: a message-naming fix (naming the file a fallback read actually read, not always /etc/os-release), dead-code removal, stronger test assertions on the simulated-host-root test, a probe_failures filter fix so it matches either os-release path, and switching the WSL-adjacent verify: check from readability-based to existence-based - the same bug class the original fallback fix addressed. I independently re-verified all of these are currently correct in the code with no regression; no blocking findings remain.
The new OsRelease (Found/Absent/Unreadable) enum is a clean replacement for the old lossy Option<String>, and the "fall back only on NotFound, never on another error" discipline is correct on both the Rust and WSL-shell sides, with regression tests guarding against reintroducing a hardcoded path.
Two non-blocking notes for a future pass, not blocking this:
- There's a pre-existing (not introduced here) WSL-guest edge case where a permission-denied /etc/os-release aborts the shell probe entirely rather than degrading gracefully. Already disclosed as a known, scoped-out gap in a test doc-comment.
- The new
verify:fallback one-liners in diagnose.rs/fix.rs aren't exercised under a real shell, unlike the structurally identical WSL probe fragment which does get one.
On spec: the fallback matches issue #545 exactly and all original call sites are covered. The PR does go a bit beyond the issue's literal ask (the Unreadable tri-state, not just absent-fallback), but that's disclosed in the PR's own Scope section and is well-tested, not scope creep slipped in quietly.
CI is effectively green; only windows-build-and-test was still pending at last check.
…nt split PR ROCm#675 added an OsRelease-aware `_read` seam alongside the existing bare-text and `_at` functions in driver_install.rs and openmpi.rs. That left three entry points per builder, with the bare-text ones kept only because tests had no OsRelease of their own to construct. Now that tests can build `OsRelease::found(text)` directly, migrate the 33 build_driver_install_plan call sites and 12 install_hint_for call sites to their _read equivalents, and delete the two now-unreferenced bare functions. The _at functions are untouched; they remain the real implementation both _read and any other path-aware caller route through. No behavior change. Fixes ROCm#690 Signed-off-by: Jussi Elo <jussi.elo@amd.com>
… driver_install.rs (ROCm#705) * chore(driver_install, openmpi): collapse the bare/_at/_read entry-point split PR ROCm#675 added an OsRelease-aware `_read` seam alongside the existing bare-text and `_at` functions in driver_install.rs and openmpi.rs. That left three entry points per builder, with the bare-text ones kept only because tests had no OsRelease of their own to construct. Now that tests can build `OsRelease::found(text)` directly, migrate the 33 build_driver_install_plan call sites and 12 install_hint_for call sites to their _read equivalents, and delete the two now-unreferenced bare functions. The _at functions are untouched; they remain the real implementation both _read and any other path-aware caller route through. No behavior change. Fixes ROCm#690 Signed-off-by: Jussi Elo <jussi.elo@amd.com> * docs(testing): use relative markdown links for the two file citations Addresses a review comment: a bare backtick path bypasses link validation, so a moved or deleted target would go unnoticed. Signed-off-by: Jussi Elo <jussi.elo@amd.com> * Update crates/rocm-core/src/openmpi.rs Co-authored-by: Tomas Saaristola <144015745+tomastola@users.noreply.github.com> Signed-off-by: Jussi Elo <jussi.elo@amd.com> --------- Signed-off-by: Jussi Elo <jussi.elo@amd.com> Co-authored-by: Tomas Saaristola <144015745+tomastola@users.noreply.github.com>
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 4b3d460
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
Round 2. This PR was merged at exactly this head (4b3d460) while the round was running, and the earlier automated change request (at 5f2c3ff) had already been dismissed. Nothing here gates anything any more. The findings describe code that is now on main; treat the blocking ones as follow-up candidates. Each finding is marked new or standing (re-found blind in a narrower form).
Blockers from the 5f2c3ff round that this round did not re-find (the cargo fmt failure, the untested path-naming messages and WSL/Windows guard, the examine-24 step that could not fail, the missing scenario for an unreadable release file) were not put through a separate objection check, because the change request holding them was already dismissed and the PR merged. CI on this head is all green, and examine-28 and per-call-site tests were added for them. That is the extent of what this round says about them.
Review — needs work
Full review of the whole change.
Implements all of #545: one shared reader for /etc/os-release that falls back to /usr/lib/os-release, used at every read site. #545 is already CLOSED on the tracker.
Blocking
-
[new] The examine-28 step checks less than its wording says, and the new distro-name line has no scenario —
tests/e2e-cucumber/tests/e2e/examine_steps.rs:1551-1577,crates/rocm-core/src/host_gpu.rs:1976-1990- The step says "its only failure says the release file could not be read". It filters
probe_failuresdown to entries containing"os-release"and then checkslen() == 1. Any other probe failure could be what degrades the verdict, and the step would still pass. - The sibling examine-23 step asserts the whole
probe_failureslist, on the same GPU fixtures. - The step also never reads
/summary/distro. That field now shows a new line users can see:Linux (/etc/os-release could not be read: …). - In mutation testing by a worker, removing the
Unreadablearm indistro_name_from_os_releasebroke no scenario; only a unit test catches it. - This conflicts with AGENTS.md §3: user-observable output needs a scenario, and a message is asserted together with the behaviour.
- Confidence 85 · logic · Fix: assert the full
probe_failureslist, as examine-23 does (prefix/suffix match for the OS error text). Also assert/summary/distrostarts withLinux (/etc/os-release could not be read:.
- The step says "its only failure says the release file could not be read". It filters
-
[standing, narrower form] The WSL probe's shell test cannot fail on the claim it is named for —
crates/rocm-core/src/examine.rs(thetry_os_release_does_not_source_the_fallback_when_the_primary_is_not_sourceabletest and theTRY_OS_RELEASE_FNdoc),docs/testing.md:596-604- The doc and docs/testing.md say the test proves "existence is not readability" (
[ -e ]rather than a readability check). - A worker changed
[ -e "$1" ]to[ -r "$1" ]in bothTRY_OS_RELEASE_FNandWSL_REMOTE_PROBE, and all 96 tests stayed green. A directory is readable, so-rtreats the fixture exactly as-edoes. - The fixture only tells
-eapart from-f. The-rregression the doc is guarding against, which would source a stale fallback in place of a permission-denied primary, goes undetected. - Confidence 85 · logic · Fix: either reword the doc to the claim the fixture actually supports, or add a mode-000 case that is skipped when running as uid 0.
- The doc and docs/testing.md say the test proves "existence is not readability" (
-
[new] The PR description repeats two claims the code does not support — PR body, "Tests" section
- "A shell-level test … confirming existence is not readability" — the finding above shows it does not.
- "WSL has its own guest-side probe, so the local Rust-side read is never authoritative for it." The same reasoning is in the
a_wsl_host_ignores_an_unreadable_local_os_release_readdoc (apps/rocm/src/driver_install.rs:2046-2054).WSL_REMOTE_PROBEis run from a Windows host into a distro overwsl.exe(examine.rs:848).- On a WSL guest,
rocm install driverruns inside the guest, so the local read is the guest's own file. - The guard holds for another reason:
wsl_rocdxg_driver_plannever looks at the distro.
- Confidence 70 · logic · Fix: correct the test doc (the description no longer matters now the PR is merged). The code is fine as written.
Non-blocking
-
[new] The new wsl-5
verifycommand always exits 0 and prints nothing when there is no file or it cannot be read —crates/rocm-core/src/diagnose.rs:2424,crates/rocm-core/src/fix.rs:749- I ran it under
sh. With neither file present: empty output, rc 0. With/etc/os-releaseas a directory: empty, rc 0. With noVERSION_IDline: empty, rc 0. - The old
grep VERSION_ID /etc/os-releaseshowed an error for the first two cases. 2>/dev/nullis left over from the two-file grep in 6731fe8. It now hides the permission-denied case that 31881ce's message is about.| head -1makes the pipeline's exit statushead's, and picks the first match, whereas the parser lets the last assignment win. An unanchoredVERSION_IDalso matched aVERSION_ID_LIKE=1line I planted ahead of the real one.- No test pins either copy of the string. The diagnose/fix divergence test does not cover this recipe.
- Confidence 85 · logic · Fix:
grep '^VERSION_ID=' "$f"with no redirect and nohead, plus a divergence check covering this recipe.
- I ran it under
-
[new] The duplicated version default contradicts its own doc comment —
apps/rocm/src/driver_install.rs:825-837,:863unreadable_os_release_plan's doc says sharingrepo_version's resolution means "a bumped default … is not silently absent from the other".- In fact
"${ROCM_CLI_AMDGPU_VERSION:-7.2.4}"is now hard-coded both there and inbuild_driver_install_plan_at. Bumping one leaves the other stale. - No test asserts the unreadable plan's
repo_version: the worker's mutation setting it toString::new()survived. - Confidence 85 · mechanical · Fix: one
const, used by both.
-
[new]
host_path_for_str/identity_path_for_strexist for a reason that does not hold —crates/rocm-core/src/hardware_root.rs:81-105- The docs say callers pass these "instead of
|p| host_path(p)" / "|p| PathBuf::from(p)", because barehost_path/PathBuf::fromare rejected. - The bare forms are indeed rejected. But I compiled both closures against an
impl Fn(&str) -> PathBufparameter and they are accepted; the crate's own tests pass|name| candidate(dir.path(), name)the same way. - Two new
pubfunctions therefore add API surface that a closure would do without. - Confidence 85 · mechanical · Fix: use closures and drop the helpers, or fix the docs to say they are a naming convenience.
- The docs say callers pass these "instead of
-
[new] Stale comment —
apps/rocm/src/runtime_install_finalize.rs:2133-2135refers to the helper's ".context(...)call". That call was removed along with the oldResult-returningread_os_release. Confidence 85 · mechanical · Fix: drop the clause. -
[new] Comments narrate history instead of describing current state —
hardware_root.rs(OsReleasedoc: "Before this distinction existed…"),examine.feature(examine-28 comment: "Before this change… It now reports"),host_gpu.rs(test doc "new behavior; it used to be…", and the "pre-fallback behaviour this function has always had" arm comment),docs/testing.md("now gets", "now read through"). These go stale on merge. Confidence 85 · mechanical · Fix: state current behaviour only. -
[new] examine-23 and examine-28 have the same title —
tests/e2e-cucumber/features/examine.feature:324,:427. Both read "An unreadable release file degrades the verdict on a machine with a GPU".feature_naming(passes, 4 tests) does not check title uniqueness, so reports cannot tell the two apart. Confidence 70 · mechanical · Fix: retitle examine-28 to say the file itself cannot be read. -
[new]
detect_distro_name'sAbsent => Nonearm is not pinned —crates/rocm-core/src/host_gpu.rs:1976. In a worker's mutation run, changing it toSome("Linux")broke no test. The arm keeps existing behaviour rather than adding new, so this is not blocking. Confidence 70 · logic · Fix: add a fallback-root case with neither file present to the feature-gated test. -
[new] The fallback in the real-host install readers is reachable only through the delegation —
crates/rocm-core/src/openmpi.rs:1014,apps/rocm/src/runtime_install_finalize.rs:160- In a worker's mutation runs, replacing either
real_os_release()body with an/etc-only read that still returns anOsReleasepassed every test. - The
a_fallback_read_plans_*tests compare two inputs whose path is never used for a well-formed file, so they killed none of the path mutants. - The PR body states this gap honestly. The test docs saying "reverting … is a type error" hold only for a revert to
String. - Confidence 70 · logic · Fix: narrow those docs, or call
read_os_releasefrom a test with an injected resolver that mimicsidentity_path_for_str.
- In a worker's mutation runs, replacing either
Decisions for the author
reaches_os_release_parseduplicatesbuild_driver_install_plan_at's Windows/WSL early returns (apps/rocm/src/driver_install.rs:1026) — tradeoff.- For: it keeps the
Unreadablecheck outside_at, which stays text-based for its many existing tests. - Against: it is a twin predicate that will drift if a new early return is added. Each half is pinned by its own test today.
- For: it keeps the
Positive signals
read_os_releasefalls back only onNotFound. Each candidate's non-NotFounderror has its own test, so a stale/usr/lib/os-releasecan never replace a present-but-unreadable primary.try_os_release_fn_is_embedded_in_the_wsl_remote_probestops the tested shell constant from silently drifting away from the copy that actually runs.
Deployment notes
rocm examine --jsonnow reportsstatus: degraded, with a newprobe_failuresentry, on hosts where/etc/os-releaseexists but cannot be read. The install plans get a newunreadable_os_releaseoutcome for that case. Consumers keyed onstatuswill see that change.
What this covered
Read every file in the change completely, plus the callers of the changed and removed functions across the workspace. Diff 0716645 (merge-base)…4b3d460; prw-base is at 80ca67f. The branch merges main, so history was read with --first-parent.
Two independent read-only workers, grouped by coupling:
- Install-plan consumers (
openmpi.rs,driver_install.rs,runtime_install_finalize.rs, withhardware_root.rs). Focused unit tests: 42 rocm-core and 63 rocm passed. Clippy clean. 15 single-branch mutations. - The reader, examine, host_gpu, diagnose/fix, e2e and docs, plus the agent-instruction, history and code-comment passes and a sibling-reader grep. 47 rocm-core tests passed with
e2e-test-hooks.feature_naming4/4. Mutation runs.
I compiled a closure-inference check and ran the verify shell string myself.
Every check on the head commit passed, including every required check, E2E tests and windows-build-and-test.
Did not run:
- The e2e suite; examine-27/28 verdicts are reasoned.
- A Windows-target
cargo check: there is no mingw here. - The prior-changes pass: earlier review comments were deliberately not read.
Not reconciled against any PR discussion.


#544 merged into
main. This PR was rebased directly ontomain: the commits it had inherited fromfix/os-release-one-parserwere superseded by #544's squash merge and dropped, keeping only this PR's own contribution.Fixes #545.
The problem
os-release(5)says to read/etc/os-releaseand, if that file does not exist, fall back to/usr/lib/os-release. Every reader in rocm-cli read only/etc/os-release, so on a host that ships the file only at/usr/lib/os-release(minimal or container images, some distro variants), the distro read as unknown:rocm examinereported nothing, androcm install driverplanned nothing.The fix
Adds one shared helper,
hardware_root::read_os_release, that tries/etc/os-releasethen/usr/lib/os-release, parameterized by a path-resolution closure, and falls back only when the primary is absent — never for another read error (permission denied, a directory in its place), so a merely-unreadable primary is never silently replaced by a possibly stale fallback. It returns anOsReleaseenum (Found/Absent/Unreadable) rather than a bare string, so a file that exists but could not be read is reported as itself — a newprobe_failuresentry andstatus: degradedfromrocm examine, and a dedicatedunreadable_os_releasepolicy from the install plans — distinctly from both a malformed line and an unknown distribution. Before this, that case was silently folded into an empty, unreported distro.#544 had already consolidated the original read sites down to two functions that do all the in-process reading (
examine.rs/host_gpu.rs'shost_path-routed distro probes, andopenmpi.rs's/runtime_install_finalize.rs's real-host package-manager readers); wiring the fallback in required changing those two, plus a seventh site this PR's own review caught: the WSL remote-distro shell probe, which runs in the WSL guest rather than this process and so carries its own embedded shell copy of the same existence-based fallback. Every message that names an unreadable line or file now names the candidate actually read (/etc/os-releaseor/usr/lib/os-release), not always the former.Tests
rocm examine's probe failures, and the distro name) pinning that a fallback-sourced read plans/reports identically to a primary-sourced read with the same text, that a malformed line read through the fallback path names the fallback path rather than a hardcoded/etc/os-release, and that a present-but-unreadable file is reported as itself rather than as an unknown distribution.Unreadablebranch there: WSL has its own guest-side probe, so the local Rust-side read is never authoritative for it./bin/sh, confirming existence is not readability — see its own doc comment for the one case it cannot cover under CI without root (a genuinely permission-denied primary), which predates this PR and is out of scope here.examine-27plants only/usr/lib/os-releaseto exercise the fallback end to end, andexamine-28plants a directory in place of/etc/os-release(a non-NotFounderror needing no root privilege) to exercise the degraded verdict and failure message. (Numbered 27/28, not 24/25: fix(rocmd): drain child pipes concurrently so output size stops faking timeouts #526's amd-smi telemetry scenarios claimed 24-26 in the same feature file first.)/etc/os-releasedisappear to exercise it. Covered by the shared helper's own unit tests instead — the same gap fix: read /etc/os-release with one parser that matches sh #544 already states for this exact split.Docs updated in the same change:
hardware_root.rs's module doc,docs/release-trust.md's Simulated Host Root section, anddocs/testing.md's/etc/os-releasesection.Scope
openmpi::plan_for_os_release— a non-testpub fnwith no production caller, made redundant by theOsRelease-aware seam this PR adds — is deleted.install_hint_forandbuild_driver_install_planstay as bare-text test helpers: they're load-bearing for dozens of unrelated test scenarios (privilege escalation, WSL plans, AMDGPU version overrides, quote-style parsing), not redundant plumbing, so collapsing them is left to a follow-up (#690) rather than bundled in here.The two
verify:hint strings infix.rs/diagnose.rs(shown to a WSL user as manual-verification advice) are now fallback-aware too, since they were the last os-release readers in the tree without it.Verification
cargo build --workspace— clean.cargo test --workspace --all-targets— every file this PR touches passed cleanly.cargo clippy --workspace --all-targets -- -D warnings— clean.cargo fmt --all -- --check— clean.cargo xtask check-crate-edges— clean, no new cross-crate dependency edges.tests/feature_naming.rs— clean (new scenario is sequential, unique, feature-qualified).cargo xtask e2e -- -n 'examine-2[0-3] |examine-2[7-8] '— 6 scenarios, 22 steps, all passed against a live run.