max / alloy
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
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!( |