Skip to main content

max / alloy

Look for rfkill outside PATH as well rfkill ships in /usr/sbin, and a booted Alloy session has neither /sbin nor /usr/sbin on PATH (problem d0dc9dad, measured on fw12). Fedora 43 unifies bin and sbin so the bare name probably resolves, but that cannot be checked from fw13, and the failure is silent: an errored radio read becomes Radio::Unknown, which reads as "no rfkill here" and drops hard-block detection without saying anything. Falls back to the absolute path, one extra spawn where the first misses.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-05 17:43 UTC
Signed with PGP, not checked
Commit: 8ba774d0cc372299e48f25f30cc558ae6c1774c1
Parent: 151a0de
1 file changed, +45 insertions, -13 deletions
@@ -454,19 +454,9 @@
454 454 }
455 455
456 456 fn radio(&self) -> Radio {
457 - // `--noheadings --output` rather than the default listing or `--json`:
458 - // util-linux 2.39 accepts `--json` and ignores it, printing the human
459 - // format, so a JSON parser here would fail on the machine it was
460 - // written for. Measured on fw13, 2026-08-05.
461 - Invocation::new("rfkill")
462 - .args([
463 - "--noheadings",
464 - "--output",
465 - "TYPE,SOFT,HARD",
466 - "list",
467 - "bluetooth",
468 - ])
469 - .capture_quiet()
457 + RFKILL_PROGRAMS
458 + .iter()
459 + .find_map(|program| rfkill_invocation(program).capture_quiet().ok())
470 460 .map_or(Radio::Unknown, |raw| parse_rfkill(&raw))
471 461 }
472 462
@@ -571,6 +561,35 @@
571 561 }
572 562 }
573 563
564 + /// Where to look for rfkill, in order.
565 + ///
566 + /// The bare name first, and an absolute path after it, because the console
567 + /// cannot count on `PATH` carrying `/usr/sbin`. A booted Alloy session was
568 + /// measured on fw12 with a `PATH` of exactly
569 + /// `~/.local/bin:~/.cargo/bin:/usr/local/bin:/usr/bin:/usr/local/sbin`, no
570 + /// `/sbin` and no `/usr/sbin` (GoingsOn problem `d0dc9dad`), and rfkill ships in
571 + /// `/usr/sbin`. Fedora 43 unifies bin and sbin, so the bare name probably
572 + /// resolves there and this costs one extra spawn on a machine where it does not.
573 + ///
574 + /// Worth the spawn because the failure is silent: a radio read that errors
575 + /// becomes [`Radio::Unknown`], which reads on screen as "no rfkill here" and
576 + /// quietly drops the hard-block detection rather than reporting a fault.
577 + const RFKILL_PROGRAMS: [&str; 2] = ["rfkill", "/usr/sbin/rfkill"];
578 +
579 + /// `--noheadings --output` rather than the default listing or `--json`:
580 + /// util-linux 2.39 accepts `--json` and ignores it, printing the human format,
581 + /// so a JSON parser here would fail on the machine it was written for. Measured
582 + /// on fw13, 2026-08-05.
583 + fn rfkill_invocation(program: &str) -> Invocation {
584 + Invocation::new(program).args([
585 + "--noheadings",
586 + "--output",
587 + "TYPE,SOFT,HARD",
588 + "list",
589 + "bluetooth",
590 + ])
591 + }
592 +
574 593 // ---- parsing ----
575 594
576 595 /// One `Key: value` field from `show` or `info` output.
@@ -1774,6 +1793,19 @@
1774 1793 assert!(device.caveat().unwrap().contains("does not survive"));
1775 1794 }
1776 1795
1796 + /// rfkill lives in `/usr/sbin`, and a booted Alloy session has no
1797 + /// `/usr/sbin` in `PATH` (problem `d0dc9dad`). The absolute path is the
1798 + /// fallback, and its absence would silently drop hard-block detection
1799 + /// rather than reporting anything.
1800 + #[test]
1801 + fn rfkill_is_looked_for_outside_path_as_well() {
1802 + assert_eq!(RFKILL_PROGRAMS, ["rfkill", "/usr/sbin/rfkill"]);
1803 + assert_eq!(
1804 + rfkill_invocation("/usr/sbin/rfkill").display(),
1805 + "/usr/sbin/rfkill --noheadings --output TYPE,SOFT,HARD list bluetooth"
1806 + );
1807 + }
1808 +
1777 1809 #[test]
1778 1810 fn rfkill_reports_hard_ahead_of_soft() {
1779 1811 assert_eq!(