use log::LevelFilter; use serde_json::{Map, Value}; use std::convert::TryInto; use std::path::Path; // Directory for Bluetooth hci devices pub const HCI_DEVICES_DIR: &str = "/sys/class/bluetooth"; // File to store the Bluetooth daemon to use (bluez or floss) const BLUETOOTH_DAEMON_CURRENT: &str = "/var/lib/bluetooth/bluetooth-daemon.current"; // File to store the config for BluetoothManager const BTMANAGERD_CONF: &str = "/var/lib/bluetooth/btmanagerd.json"; /// Folder to keep files which override floss configuration const FLOSS_SYSPROPS_OVERRIDE_DIR: &str = "/var/lib/bluetooth/sysprops.conf.d"; /// File to store bluetooth devcoredump configuration. This file is used by the /// udev rule to copy the devcoredump state to .../device/coredump_enabled sysfs /// entry. It is also used by the user-space crash reporter to enable/disable /// parsing of the devcoredump. const FLOSS_COREDUMP_CONF_PATH: &str = "/var/run/bluetooth/coredump_disabled"; /// Key used for default adapter entry. const DEFAULT_ADAPTER_KEY: &str = "default_adapter"; /// In the absence of other values, default to hci0. const DEFAULT_ADAPTER_VALUE: i32 = 0; pub fn is_floss_enabled() -> bool { match std::fs::read(BLUETOOTH_DAEMON_CURRENT) { Ok(v) => { let content = std::str::from_utf8(&v); match content { Ok(version) => version.contains("floss"), Err(_) => false, } } Err(_) => false, } } pub fn write_floss_enabled(enabled: bool) -> bool { std::fs::write( BLUETOOTH_DAEMON_CURRENT, match enabled { true => "floss", _ => "bluez", }, ) .is_ok() } pub fn read_config() -> std::io::Result { std::fs::read_to_string(BTMANAGERD_CONF) } pub fn get_log_level() -> Option { get_log_level_internal(read_config().ok()?) } fn get_log_level_internal(config: String) -> Option { serde_json::from_str::(config.as_str()) .ok()? .get("log_level")? .as_str()? .parse::() .ok() } /// Returns whether hci N is enabled in config; defaults to true. pub fn is_hci_n_enabled(n: i32) -> bool { match read_config().ok().and_then(|config| is_hci_n_enabled_internal(config, n)) { Some(v) => v, _ => true, } } fn is_hci_n_enabled_internal(config: String, n: i32) -> Option { serde_json::from_str::(config.as_str()) .ok()? .get(format!("hci{}", n))? .as_object()? .get("enabled")? .as_bool() } // When we initialize BluetoothManager, we need to make sure the file is a well-formatted json. pub fn fix_config_file_format() -> bool { match read_config() { Ok(s) => match serde_json::from_str::(s.as_str()) { Ok(_) => true, _ => std::fs::write(BTMANAGERD_CONF, "{}").is_ok(), }, _ => std::fs::write(BTMANAGERD_CONF, "{}").is_ok(), } } pub fn modify_hci_n_enabled(n: i32, enabled: bool) -> bool { if !fix_config_file_format() { false } else { match read_config() .ok() .and_then(|config| modify_hci_n_enabled_internal(config, n, enabled)) { Some(s) => std::fs::write(BTMANAGERD_CONF, s).is_ok(), _ => false, } } } fn modify_hci_n_enabled_internal(config: String, n: i32, enabled: bool) -> Option { let hci_interface = format!("hci{}", n); let mut o = serde_json::from_str::(config.as_str()).ok()?; match o.get_mut(hci_interface.clone()) { Some(section) => { section.as_object_mut()?.insert("enabled".to_string(), Value::Bool(enabled)); serde_json::ser::to_string_pretty(&o).ok() } _ => { let mut entry_map = Map::new(); entry_map.insert("enabled".to_string(), Value::Bool(enabled)); o.as_object_mut()?.insert(hci_interface, Value::Object(entry_map)); serde_json::ser::to_string_pretty(&o).ok() } } } pub fn get_default_adapter() -> i32 { match read_config().ok().and_then(|config| { serde_json::from_str::(config.as_str()).ok()?.get(DEFAULT_ADAPTER_KEY)?.as_i64() }) { Some(v) => v.try_into().unwrap_or(DEFAULT_ADAPTER_VALUE), None => DEFAULT_ADAPTER_VALUE, } } pub fn set_default_adapter(adapter: i32) -> bool { match read_config().ok().and_then(|config| { let mut cfg = serde_json::from_str::(config.as_str()).ok()?; cfg[DEFAULT_ADAPTER_KEY] = serde_json::to_value(adapter).ok().unwrap(); serde_json::ser::to_string_pretty(&cfg).ok() }) { Some(s) => std::fs::write(BTMANAGERD_CONF, s).is_ok(), None => false, } } pub fn list_hci_devices() -> Vec { hci_devices_string_to_int(list_hci_devices_string()) } fn list_hci_devices_string() -> Vec { match std::fs::read_dir(HCI_DEVICES_DIR) { Ok(entries) => entries .map(|e| e.unwrap().path().file_name().unwrap().to_str().unwrap().to_string()) .collect::>(), _ => Vec::new(), } } /// Check whether a certain hci device exists in sysfs. pub fn check_hci_device_exists(hci: i32) -> bool { Path::new(format!("{}/hci{}", HCI_DEVICES_DIR, hci).as_str()).exists() } /// Get the devpath for a given hci index. This gives a stable path that can be /// used to identify a device even as the hci index fluctuates. pub fn get_devpath_for_hci(hci: i32) -> Option { match std::fs::canonicalize(format!("{}/hci{}/device", HCI_DEVICES_DIR, hci).as_str()) { Ok(p) => Some(p.into_os_string().into_string().ok()?), Err(e) => { log::debug!("Failed to get devpath for hci{} with error: {}", hci, e); None } } } fn hci_devices_string_to_int(devices: Vec) -> Vec { devices .into_iter() .filter_map(|e| if e.starts_with("hci") { e[3..].parse::().ok() } else { None }) .collect() } pub fn list_pid_files(pid_dir: &str) -> Vec { match std::fs::read_dir(pid_dir) { Ok(entries) => entries .map(|e| e.unwrap().path().file_name().unwrap().to_str().unwrap().to_string()) .collect::>(), _ => Vec::new(), } } /// Calls the reset sysfs entry for an hci device. Returns True if the write succeeds. pub fn reset_hci_device(hci: i32) -> bool { let path = format!("/sys/class/bluetooth/hci{}/reset", hci); std::fs::write(path, "1").is_ok() } pub fn read_floss_ll_privacy_enabled() -> std::io::Result { let parent = Path::new(FLOSS_SYSPROPS_OVERRIDE_DIR); if !parent.is_dir() { return Ok(false); } let data = std::fs::read_to_string(format!( "{}/{}", FLOSS_SYSPROPS_OVERRIDE_DIR, "privacy_override.conf" ))?; Ok(data == "[Sysprops]\nbluetooth.core.gap.le.privacy.enabled=true\n") } pub fn write_floss_ll_privacy_enabled(enabled: bool) -> std::io::Result<()> { let parent = Path::new(FLOSS_SYSPROPS_OVERRIDE_DIR); std::fs::create_dir_all(parent)?; let data = format!( "[Sysprops]\nbluetooth.core.gap.le.privacy.enabled={}", if enabled { "true\n" } else { "false\n" } ); std::fs::write(format!("{}/{}", FLOSS_SYSPROPS_OVERRIDE_DIR, "privacy_override.conf"), data) } pub fn set_adapter_coredump_state(enabled: bool) -> std::io::Result<()> { let data = format!("{}\n", !enabled as i32); for hci in list_hci_devices_string() { let path = format!("{}/{}/device/coredump_disabled", HCI_DEVICES_DIR, hci); std::fs::write(path, &data)?; } Ok(()) } pub fn write_coredump_state_to_file(enabled: bool) -> bool { let data = format!("{}\n", !enabled as i32); if let Err(e) = std::fs::write(FLOSS_COREDUMP_CONF_PATH, data) { log::error!("Failed to write devcoredump state (error: {})", e); return false; } if let Err(e) = set_adapter_coredump_state(enabled) { log::error!("Failed to set devcoredump state (error: {})", e); return false; } true } #[cfg(test)] mod tests { use super::*; fn is_hci_n_enabled_internal_wrapper(config: String, n: i32) -> bool { is_hci_n_enabled_internal(config, n).or(Some(true)).unwrap() } #[test] fn parse_log_level() { assert_eq!( get_log_level_internal("{\"log_level\": \"error\"}".to_string()).unwrap(), LevelFilter::Error ); assert_eq!( get_log_level_internal("{\"log_level\": \"warn\"}".to_string()).unwrap(), LevelFilter::Warn ); assert_eq!( get_log_level_internal("{\"log_level\": \"info\"}".to_string()).unwrap(), LevelFilter::Info ); assert_eq!( get_log_level_internal("{\"log_level\": \"debug\"}".to_string()).unwrap(), LevelFilter::Debug ); assert_eq!( get_log_level_internal("{\"log_level\": \"trace\"}".to_string()).unwrap(), LevelFilter::Trace ); assert_eq!( get_log_level_internal("{\"log_level\": \"random\"}".to_string()).is_none(), true ); } #[test] fn parse_hci0_enabled() { assert_eq!( is_hci_n_enabled_internal_wrapper("{\"hci0\":\n{\"enabled\": true}}".to_string(), 0), true ); } #[test] fn modify_hci0_enabled() { let modified_string = modify_hci_n_enabled_internal("{\"hci0\":\n{\"enabled\": false}}".to_string(), 0, true) .unwrap(); assert_eq!(is_hci_n_enabled_internal_wrapper(modified_string, 0), true); } #[test] fn modify_hci0_enabled_from_empty() { let modified_string = modify_hci_n_enabled_internal("{}".to_string(), 0, true).unwrap(); assert_eq!(is_hci_n_enabled_internal_wrapper(modified_string, 0), true); } #[test] fn parse_hci0_not_enabled() { assert_eq!( is_hci_n_enabled_internal_wrapper("{\"hci0\":\n{\"enabled\": false}}".to_string(), 0), false ); } #[test] fn parse_hci1_not_present() { assert_eq!( is_hci_n_enabled_internal_wrapper("{\"hci0\":\n{\"enabled\": true}}".to_string(), 1), true ); } #[test] fn test_hci_devices_string_to_int_none() { assert_eq!(hci_devices_string_to_int(vec!["somethingelse".to_string()]), Vec::::new()); } #[test] fn test_hci_devices_string_to_int_one() { assert_eq!(hci_devices_string_to_int(vec!["hci0".to_string()]), vec![0]); } #[test] fn test_hci_devices_string_to_int_two() { assert_eq!( hci_devices_string_to_int(vec!["hci0".to_string(), "hci1".to_string()]), vec![0, 1] ); } }