redbear-btctl: convert StubBackend::from_env panic to Result propagation

The single production-code panic in the redbear-* codebase was in
StubBackend::from_env() (backend.rs:192). It panicked when the
REDBEAR_BTCTL_STUB_ADAPTERS env var contained an invalid Bluetooth adapter
name.

Previously:
  - from_env() returned Self (infallible)
  - build_backend() returned Box<dyn Backend>
  - main() returned anyhow::Result<()>

After:
  - from_env() renamed to try_from_env() -> Result<Self, String>
  - build_backend() returns anyhow::Result<Box<dyn Backend>>
  - main() call sites use '?' to propagate errors
  - Adapter name validation now returns Err instead of crashing the daemon

The panic was the ONLY production-code panic across all 5 redbear-*
programs that this phase was scoped for. The other 13 panics are in
test code (#[cfg(test)] modules), which is idiomatic Rust testing
practice and does not need modification.

cargo check --manifest-path source/Cargo.toml passes cleanly.
This commit is contained in:
2026-07-28 23:36:33 +09:00
parent 599e7bda35
commit 40c5d8a701
2 changed files with 14 additions and 10 deletions
@@ -176,7 +176,7 @@ pub struct StubBackend {
}
impl StubBackend {
pub fn from_env() -> Self {
pub fn try_from_env() -> Result<Self, String> {
let adapters = parse_list(
env::var("REDBEAR_BTCTL_STUB_ADAPTERS").ok().as_deref(),
&["hci0"],
@@ -188,12 +188,14 @@ impl StubBackend {
&[],
);
for adapter in &adapters {
if validate_adapter_name(adapter).is_err() {
panic!("invalid Bluetooth adapter name in REDBEAR_BTCTL_STUB_ADAPTERS: {adapter}");
if let Err(e) = validate_adapter_name(adapter) {
return Err(format!(
"invalid Bluetooth adapter name in REDBEAR_BTCTL_STUB_ADAPTERS: {adapter}: {e}"
));
}
}
Self {
Ok(Self {
runtime_state: adapters
.iter()
.cloned()
@@ -212,7 +214,7 @@ impl StubBackend {
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("/var/run/redbear-btusb/status")),
bond_store: BondStore::from_env(),
}
})
}
#[cfg(test)]
@@ -73,12 +73,14 @@ fn notify_scheme_ready(notify_fd: Option<RawFd>, socket: &Socket, scheme: &mut B
}
}
fn build_backend() -> Box<dyn Backend> {
fn build_backend() -> anyhow::Result<Box<dyn Backend>> {
let backend_type = env::var("REDBEAR_BTCTL_BACKEND")
.unwrap_or_else(|_| "stub".to_string());
match backend_type.as_str() {
"hci" => Box::new(hci_backend::HciBackend::from_env()),
_ => Box::new(StubBackend::from_env()),
"hci" => Ok(Box::new(hci_backend::HciBackend::from_env())),
_ => Ok(Box::new(StubBackend::try_from_env().map_err(|e| {
anyhow::anyhow!("{e}")
})?)),
}
}
@@ -424,7 +426,7 @@ fn main() -> anyhow::Result<()> {
init_logging(log_level);
let args = env::args().skip(1).collect::<Vec<_>>();
let mut backend = build_backend();
let mut backend = build_backend()?;
match execute(&args, backend.as_mut()) {
Ok(Some(output)) => {
@@ -452,7 +454,7 @@ fn main() -> anyhow::Result<()> {
process::exit(1);
}
};
let mut scheme = BtCtlScheme::new(build_backend());
let mut scheme = BtCtlScheme::new(build_backend()?);
let mut state = redox_scheme::scheme::SchemeState::new();
notify_scheme_ready(notify_fd, &socket, &mut scheme);