To potentially also port starship_battery on OpenBSD and maybe provide a better safe sysctl abstraction for any OS that supports libc::sysctl. I started to work on the best abstraction I could.
Do not hesitate to make some feedback:
use libc::c_void;
use std::error;
use std::mem;
use std::ptr;
use std::slice::from_raw_parts_mut;
const CTL_HW: i32 = 6;
const HW_SENSORS: i32 = 11;
const HW_MODEL: i32 = 2;
const SENSOR_MAX_TYPES: usize = 23;
#[repr(C)]
#[derive(Default)]
struct SensorDev {
num: i32, /* sensordev number */
xname: [u8; 16], /* unix device name */
maxnumt: [i32; SENSOR_MAX_TYPES],
sensors_count: i32,
}
// T must match requirements for from_raw_parts_mut.
fn as_bytes_ref<'a, T: ?Sized>(data: &mut T, len: usize) -> &'a mut [u8] {
unsafe { from_raw_parts_mut(ptr::from_mut(data) as *mut u8, len) }
}
fn sysctl(
name: &[i32],
namelen: u32,
old: Option<&mut [u8]>,
new: Option<&mut [u8]>,
) -> Result<usize, Box<dyn error::Error>> {
let mib: *const i32 = name.as_ptr();
let mut oldlenp: usize = 0;
let oldp: *mut c_void = match old {
Some(o) => {
oldlenp = o.len();
o.as_mut_ptr() as *mut c_void
}
None => ptr::null_mut(),
};
let mut newlen: usize = 0;
let newp: *mut c_void = match new {
Some(n) => {
newlen = n.len();
n.as_mut_ptr() as *mut c_void
}
None => ptr::null_mut(),
};
unsafe {
if libc::sysctl(
mib,
namelen,
oldp,
ptr::from_mut(&mut oldlenp),
newp,
newlen,
) == -1
{
return Err(std::io::Error::last_os_error().into());
}
}
Ok(oldlenp)
}
fn test1() {
let mib = [CTL_HW, HW_MODEL, 0, 0, 0];
let mut model: Vec<u8>;
let len = sysctl(&mib, 2, None, None).unwrap();
model = Vec::<u8>::with_capacity(len);
model.resize(len, 0);
sysctl(&mib, 2, Some(as_bytes_ref(&mut model[0], len)), None).unwrap();
println!("{} {}", len, String::from_utf8_lossy(model.as_slice()));
println!("Done");
}
fn test2() {
let mut mib = [CTL_HW, HW_SENSORS, 0, 0, 0];
let mut device = SensorDev::default();
while let Ok(_) = sysctl(
&mib,
3,
Some(as_bytes_ref(&mut device, mem::size_of::<SensorDev>())),
None,
) {
println!("{}", String::from_utf8_lossy(&device.xname));
mib[2] += 1;
}
println!("Done");
}
fn test3() {
let mib = [CTL_HW, HW_MODEL, 0, 0, 0];
let mut model: Vec<u8>;
let len = sysctl(&mib, 2, None, None).unwrap();
model = Vec::<u8>::with_capacity(len);
model.resize(len, 0);
sysctl(&mib, 2, Some(model.as_mut_slice()), None).unwrap();
println!("{} {}", len, String::from_utf8_lossy(model.as_slice()));
println!("Done");
}
fn main() {
test1();
test2();
test3();
}
Output on OpenBSD 7.5
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.24s
Running `target/debug/sysctl`
41 Intel(R) Core(TM) i5 CPU M 560 @ 2.67GHz
Done
cpu0
cpu1
acpibtn0
acpiac0
acpibat0
acpibat1
lisa0
itherm0
softraid0
Done
41 Intel(R) Core(TM) i5 CPU M 560 @ 2.67GHz
Done
Haven't tried on other OS yet.
Notes :
- The provided code gives more than the only two necessary functions to ease testing. (You can just cargo build it with a libc crate as dependency)
- Was not so happy with sysctl-rs approach.
- ? Sized was necessary in
as_bytes_ref
to allow for run time allocated slice buffers (necessary when dealing with string sysctl outputs where we make a first sysctl with NULL (in C) to get string length).
- Did not want to implement trait/impl for any type that would be passed as buffers for C to keep things as simple as possible (No AsRef trait or as_bytes method on structs).
- The
as_bytes_ref
implies C-style data-structures that match the description offrom_raw_parts_mut
official documentation (contiguous in memory).
- I tried simplifying sysctl parameters with macros, but Options makes it very hard and so I gave up until I get new ideas or guidance.
- I fought a lot against rust type system to concile arrays/slices of generics and generics themselves in order to convert to references to u8 slices to pass buffer to ffi in
as_bytes_ref
.
- Options with direct generics forbids implicit type reflexion (In cases of None) which was not a good fit IMHO (Why specify a constraint for a None) being the reasons of separations between sysctl (with ref to u8 slices) and as_bytes_ref (which in the end I find logic because types that provides an as_bytes or as_mut_don't need the
as_bytes_ref
).
- test1 illustrates for any slices because as test3 shows, a u8 vector does not need
as_bytes_ref
.
Non-goals :
- Port all the C enum code for any OS to work, take the codes you might need from sysctl.h when you need them (for the sake of portability/compatibility they must not be changed so often anyway)
- Maintaining a dedicated crate (Don't have time nor willingness to create an account on crates.io)
- Dealing with endianness. (conversion from i32 to slice of u8 back to i32, if on the same machine, should not need endianness management AFAIK)