svartalf / rust-battery

Rust crate providing cross-platform information about the notebook batteries.
https://crates.io/crates/battery
Apache License 2.0
354 stars 40 forks source link

NetBSD support #20

Open svartalf opened 5 years ago

svartalf commented 5 years ago

This is a tracking issue for NetBSD support.

svartalf commented 5 years ago

Blocked by the https://github.com/ebarnard/rust-plist/pull/34

rtyler commented 3 years ago

FWIW the 0.4.2 release of rust-plist has the change referenced above

svartalf commented 3 years ago

Thanks for heads up, @rtyler! I tried to tackle this issue with NetBSD installed on a VirtualBox, but it was not able to emulate battery for some reasons, so I postponed it for a while.

rtyler commented 3 years ago

I have a NetBSD-based Pinebook Pro, if you have some pointers, I could take a look at wiring this up in my spurious free time :smile_cat:

svartalf commented 3 years ago

@rtyler that would be really great, thanks!

I know nothing about NetBSD, so it would be great to start with figuring out what it can provide to us, looks like envstat tool should help us to do that?

Seems like Golang implementation uses ioctl to read the same information from /dev/sysmon fd with ENVSYS_GETDICTIONARY ioctl type (?) and it returns back proplist with whatever data there is.

When we will know what data we can operate with, we will need to check if it will be enough to fill Battery struct with it; if it is the case - everything is great, otherwise we might need to change public API first to suit NetBSD changes (I can work on that).

0-wiz-0 commented 3 years ago

A friend complained that this crate does not support NetBSD, so I took a quick look at it. Adding 'target_os = "netbsd"' to all "freebsd" cases makes it compile, but the 5 tests fail. So I guess that is not sufficient :) (NetBSD also uses acpica (same as FreeBSD) that's why I thought this might be enough.) Then I found this issue.

NetBSD's envstat source code is here: https://github.com/NetBSD/src/tree/trunk/usr.sbin/envstat

0-wiz-0 commented 3 years ago

The general framework for system monitoring and power management on NetBSD is sysmon (https://man.netbsd.org/sysmon.4) and in particular for environmental sensors, sysmon_envsys (kernel side documented here: https://man.netbsd.org/sysmon_envsys.9, but that does not really apply here). I hope this is sufficient information!

bsiegert commented 3 years ago

@svartalf What you describe (read /dev/sysmon) is exactly what envstat does too. Caveat: by default, only root may read from the sysmon device, so there should be some fallback (e.g. pretend there is no battery?).

he32 commented 3 years ago

Another piece to take a look at could be e.g. https://github.com/NetBSD/pkgsrc/tree/trunk/sysutils/xbattbar which in the patches/ sub-directory contains a patch for adapting this to NetBSD. It is considerably smaller than the envstat program. And as far as I can see, neither envstat nor xbattbar needs to be set-uid-root to work.

0-wiz-0 commented 3 years ago

Here's some sample code that gives a plist:

use std::error::Error;                                                                                                                                             
use std::fs::File;                                                                                                                                                 
use std::os::unix::io::AsRawFd;                                                                                                                                    
use nix::ioctl_readwrite;                                                                                                                                          
use std::ffi::c_void;                                                                                                                                              
use std::mem::MaybeUninit;                                                                                                                                         
use plist::Value;                                                                                                                                                  
use std::slice::from_raw_parts;                                                                                                                                    
use std::io::Cursor;                                                                                                                                               

#[allow(non_camel_case_types)]                                                                                                                                     
#[repr(C)]                                                                                                                                                         
#[derive(Debug)]                                                                                                                                                   
pub struct plistref {                                                                                                                                              
        pref_plist: *mut c_void,               /* plist data */                                                                                                    
        pref_len: usize,                /* total length of plist data */                                                                                           
}                                                                                                                                                                  

ioctl_readwrite!(envsys_getdictionary, b'E', 0, plistref);                                                                                                         

fn main() {                                                                                                                                                        
    match detect_sensors() {                                                                                                                                       
        Ok(_) => (),                                                                                                                                               
        Err(err) => println!("error: {}", err),                                                                                                                    
    }                                                                                                                                                              
}                                                                                                                                                                  

fn detect_sensors() -> Result <(), Box<dyn Error>> {                                                                                                               
    let envsys = File::open("/dev/sysmon")?;                                                                                                                       
    let mut dict = MaybeUninit::<plistref>::uninit();                                                                                                              
    let _res = unsafe { envsys_getdictionary(envsys.as_raw_fd(), dict.as_mut_ptr()) };                                                                             
    let dict = unsafe { dict.assume_init() };                                                                                                                      
    let u8slice: &[u8] = unsafe { from_raw_parts(dict.pref_plist as *const u8, dict.pref_len) };                                                                   
    let cursor = Cursor::new(u8slice);                                                                                                                             
    let value = Value::from_reader(cursor)?;                                                                                                                       
    println!("{:?}", value);                                                                                                                                       
    Ok(())                                                                                                                                                         
}

On one of my systems that prints:

Dictionary({"amdzentemp0": Array([Dictionary({"cur-value": Integer(314900000), "description": String("cpu0 temperature"), "index": String("sensor0"), "monitoring-state-refresh-event": Boolean(true), "monitoring-supported": Boolean(true), "state": String("valid"), "type": String("Temperature")}), Dictionary({"cur-value": Integer(313150000), "description": String("cpu0 ccd0 temperature"), "index": String("sensor1"), "monitoring-state-refresh-event": Boolean(true), "monitoring-supported": Boolean(true), "state": String("valid"), "type": String("Temperature")}), Dictionary({"device-properties": Dictionary({"device-class": String("other"), "refresh-timeout": Integer(30)})})]), "amdzentemp1": Array([Dictionary({"cur-value": Integer(311775000), "description": String("cpu1 temperature"), "index": String("sensor0"), "monitoring-state-refresh-event": Boolean(true), "monitoring-supported": Boolean(true), "state": String("valid"), "type": String("Temperature")}), Dictionary({"device-properties": Dictionary({"device-class": String("other"), "refresh-timeout": Integer(30)})})])})

Sorry, I don't currently have a laptop with NetBSD i.e. no battery to test with.

0-wiz-0 commented 3 years ago

Cargo dependencies:

[dependencies]
nix = "*"
plist = "*"
0-wiz-0 commented 3 years ago

I put the code here for easier testing. Pull requests welcome!

0-wiz-0 commented 2 years ago

I've extended the example code to include basic parsing of the structure and adding some example outputs, including one from a laptop with a battery. I think this now contains enough information that you should be able to extract the information you need rather easily. Can you take over from here?

naguam commented 2 months ago

A lot of project are still depending on this library instead of using Starship's fork.

I just made the full port for NetBSD.

https://github.com/starship/rust-battery/pull/69

Are you (and especially the maintainer) interested by a backport ?

I don't think it should be too hard.

The interest might be about the ffi that the Starship's fork removed.