// SPDX-License-Identifier: AGPL-3.0-or-later use napi::{Error, Result, Status}; use napi_derive::napi; #[cfg(target_os = "macos")] use sysctl::{Ctl, CtlValue, Sysctl, SysctlError}; fn invalid_name_error(function_name: &str) -> Error { Error::new( Status::InvalidArg, format!("{function_name} requires a non-empty name string"), ) } #[cfg(target_os = "macos")] fn map_sysctl_error(error: SysctlError) -> Error { match error { SysctlError::IoError(ref io_error) => { if let Some(errno) = io_error.raw_os_error() { return Error::new( Status::GenericFailure, format!("sysctlbyname failed (errno {errno})"), ); } } SysctlError::MissingImplementation | SysctlError::ExtractionError | SysctlError::ParseError | SysctlError::InvalidCStr(_) | SysctlError::InvalidCString(_) => { return Error::new( Status::GenericFailure, "sysctlbyname failed (errno 22)".to_owned(), ); } SysctlError::NotFound(_) | SysctlError::Utf8Error(_) | SysctlError::NoReadAccess | SysctlError::NoWriteAccess | SysctlError::NotSupported | SysctlError::ShortRead { .. } => {} } Error::new( Status::GenericFailure, format!("sysctlbyname failed ({error})"), ) } #[cfg(not(target_os = "macos"))] fn unsupported_platform_error() -> Error { Error::new( Status::GenericFailure, "sysctlbyname failed (errno 22)".to_owned(), ) } #[cfg(target_os = "macos")] fn ctl_for_name(name: &str) -> Result> { match Ctl::new(name) { Ok(ctl) => Ok(Some(ctl)), Err(SysctlError::NotFound(_)) => Ok(None), Err(error) => Err(map_sysctl_error(error)), } } #[cfg(target_os = "macos")] fn value_to_number(value: CtlValue) -> Result> { let number = match value { CtlValue::None => return Ok(None), CtlValue::Int(value) => f64::from(value), CtlValue::Uint(value) => f64::from(value), CtlValue::Long(value) => value as f64, CtlValue::Ulong(value) => value as f64, CtlValue::S64(value) => value as f64, CtlValue::U64(value) => value as f64, CtlValue::S32(value) => f64::from(value), CtlValue::U32(value) => f64::from(value), CtlValue::S16(value) => f64::from(value), CtlValue::U16(value) => f64::from(value), CtlValue::S8(value) => f64::from(value), CtlValue::U8(value) => f64::from(value), CtlValue::String(_) | CtlValue::Struct(_) | CtlValue::Node(_) => { return Err(Error::new( Status::GenericFailure, "sysctlbyname failed (errno 22)".to_owned(), )); } }; Ok(Some(number)) } #[cfg(target_os = "macos")] fn read_int_blocking(name: String) -> Result> { let Some(ctl) = ctl_for_name(&name)? else { return Ok(None); }; ctl.value() .map_err(map_sysctl_error) .and_then(value_to_number) } #[cfg(not(target_os = "macos"))] fn read_int_blocking(_: String) -> Result> { Err(unsupported_platform_error()) } #[cfg(target_os = "macos")] fn read_string_blocking(name: String) -> Result> { let Some(ctl) = ctl_for_name(&name)? else { return Ok(None); }; match ctl.value().map_err(map_sysctl_error)? { CtlValue::None => Ok(None), CtlValue::String(value) => Ok(Some(value)), _ => Err(Error::new( Status::GenericFailure, "sysctlbyname failed (errno 22)".to_owned(), )), } } #[cfg(not(target_os = "macos"))] fn read_string_blocking(_: String) -> Result> { Err(unsupported_platform_error()) } async fn spawn_sysctl( work: impl FnOnce() -> Result + Send + 'static, ) -> Result { tokio::task::spawn_blocking(work).await.map_err(|error| { Error::new( Status::GenericFailure, format!("sysctl worker failed: {error}"), ) })? } #[napi(js_name = "sysctlByNameInt")] pub async fn sysctl_by_name_int(name: String) -> Result> { if name.is_empty() { return Err(invalid_name_error("sysctlByNameInt")); } spawn_sysctl(move || read_int_blocking(name)).await } #[napi(js_name = "sysctlByNameString")] pub async fn sysctl_by_name_string(name: String) -> Result> { if name.is_empty() { return Err(invalid_name_error("sysctlByNameString")); } spawn_sysctl(move || read_string_blocking(name)).await } #[cfg(test)] mod tests { use super::*; #[test] fn empty_name_error_mentions_function_name() { let error = invalid_name_error("sysctlByNameInt"); assert_eq!(Status::InvalidArg, error.status); assert!(error.reason.contains("sysctlByNameInt")); } #[cfg(not(target_os = "macos"))] #[test] fn non_macos_returns_same_synthetic_errno_as_previous_js_contract() { let error = unsupported_platform_error(); assert_eq!("sysctlbyname failed (errno 22)", error.reason); } #[cfg(target_os = "macos")] #[test] fn missing_sysctl_maps_to_null() { assert!( ctl_for_name("fluxer.definitely_missing_sysctl") .unwrap() .is_none() ); } }