#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(improper_ctypes)]
#![allow(clippy::missing_safety_doc)]
#![doc = include_str!("../README.md")]
use std::ffi::{CStr, CString};
use std::fmt;
use std::io;
use std::mem::MaybeUninit;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
pub mod ffi {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
mod prelude;
pub use prelude::*;
pub struct SwitchtecDevice {
inner: *mut switchtec_dev,
}
impl SwitchtecDevice {
pub fn open<T: AsRef<Path>>(path: T) -> io::Result<Self> {
let path_c = CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|e| {
io::Error::new(io::ErrorKind::Other, e.to_string())
})?;
unsafe {
let dev = switchtec_open(path_c.as_ptr());
if dev.is_null() {
Err(get_switchtec_error())
} else {
Ok(Self { inner: dev })
}
}
}
pub fn name(&self) -> io::Result<String> {
let device_name = unsafe { switchtec_name(self.inner) };
if device_name.is_null() {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"no device name returned",
))
} else {
device_name.as_string()
}
}
pub fn boot_phase(&self) -> switchtec_boot_phase {
unsafe { switchtec_boot_phase(self.inner) }
}
pub fn firmware_version(&self) -> io::Result<String> {
const buf_size: usize = 64;
let mut buf = MaybeUninit::<[u8; buf_size]>::uninit();
unsafe {
let len =
switchtec_get_fw_version(self.inner, buf.as_mut_ptr() as *mut _, buf_size as u64);
if len.is_negative() {
Err(get_switchtec_error())
} else {
buf_to_string(&buf.assume_init())
}
}
}
pub fn generation(&self) -> switchtec_gen {
unsafe { switchtec_gen(self.inner) }
}
pub fn partition(&self) -> i32 {
unsafe { switchtec_partition(self.inner) }
}
pub fn die_temp(&self) -> io::Result<f32> {
let temp = unsafe { switchtec_die_temp(self.inner) };
if temp.is_sign_negative() {
return Err(get_switchtec_error());
}
Ok(temp)
}
}
impl fmt::Debug for SwitchtecDevice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SwitchtecDevice")
.field("name", &self.name().as_deref().unwrap_or("unknown"))
.finish()
}
}
impl std::ops::Deref for SwitchtecDevice {
type Target = *mut switchtec_dev;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::Drop for SwitchtecDevice {
fn drop(&mut self) {
unsafe {
switchtec_close(self.inner);
}
}
}
pub trait CStrExt {
fn as_string(&self) -> io::Result<String>;
}
impl CStrExt for *const i8 {
fn as_string(&self) -> io::Result<String> {
cstr_to_string(*self)
}
}
impl CStrExt for *mut i8 {
fn as_string(&self) -> io::Result<String> {
cstr_to_string(*self)
}
}
fn cstr_to_string(cstr: *const i8) -> io::Result<String> {
if cstr.is_null() {
Ok("".to_owned())
} else {
unsafe {
let s = CStr::from_ptr(cstr).to_owned();
s.into_string().map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("error decoding String from {cstr:?}: {e}"),
)
})
}
}
}
fn buf_to_string(buf: &[u8]) -> io::Result<String> {
let valid_bytes: Vec<u8> = buf
.iter()
.take_while(|b| b != &&0)
.copied()
.collect();
let cstring = CString::new(valid_bytes)?;
cstring.into_raw().as_string()
}
fn get_switchtec_error() -> io::Error {
let err_message = unsafe {
let err_str = switchtec_strerror();
if err_str.is_null() {
return io::Error::new(io::ErrorKind::Other, "Unknown error".to_owned());
}
err_str
.as_string()
.unwrap_or_else(|_| "Unknown error".to_owned())
};
io::Error::new(io::ErrorKind::Other, err_message)
}
#[test]
fn test_buf_to_string() {
let buf = [51, 46, 55, 48, 32, 66, 48, 52, 70, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(&buf_to_string(&buf).unwrap(), "3.70 B04F");
}