├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── backlight.rs ├── block.rs ├── dmi.rs ├── leds.rs ├── net.rs ├── pci.rs └── scsi_host.rs └── src ├── backlight.rs ├── block.rs ├── brightness.rs ├── dmi.rs ├── hwmon ├── fan.rs ├── mod.rs ├── pwm.rs └── temp.rs ├── leds.rs ├── lib.rs ├── net.rs ├── pci_bus └── mod.rs ├── runtime_pm.rs ├── scsi_host.rs └── sys_class.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | /target 5 | **/*.rs.bk 6 | Cargo.lock 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sysfs-class" 3 | version = "0.1.3" 4 | authors = ["Jeremy Soller "] 5 | edition = "2018" 6 | description = "Rust library for viewing /sys/class in an object-oriented format" 7 | license = "MIT" 8 | repository = "https://github.com/pop-os/sysfs-class" 9 | 10 | [dependencies] 11 | numtoa = "0.2.3" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 System76 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sysfs-class 2 | Rust library for viewing /sys/class in an object-oriented format 3 | -------------------------------------------------------------------------------- /examples/backlight.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use sysfs_class::{Backlight, Brightness, SysClass}; 3 | 4 | fn main() -> io::Result<()> { 5 | for dev in Backlight::all()? { 6 | println!( 7 | "{} brightness: {} / {}", 8 | dev.id(), 9 | dev.brightness().unwrap(), 10 | dev.max_brightness().unwrap() 11 | ); 12 | } 13 | 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /examples/block.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use sysfs_class::{Block, SysClass}; 3 | 4 | fn main() -> io::Result<()> { 5 | for block in Block::all()? { 6 | if block.has_device() { 7 | println!("Path: {:?}", block.path()); 8 | println!(" Model: {:?}", block.device_model()); 9 | println!(" Vendor: {:?}", block.device_vendor()); 10 | println!(" Rev: {:?}", block.device_rev()); 11 | println!(" Children: {:#?}", block.children()); 12 | } 13 | } 14 | 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /examples/dmi.rs: -------------------------------------------------------------------------------- 1 | use sysfs_class::DmiId; 2 | 3 | fn main() { 4 | let dmi = DmiId::default(); 5 | println!( 6 | "BIOS Date: {:?}\n\ 7 | BIOS Vendor: {:?}\n\ 8 | BIOS Version: {:?}\n\ 9 | Board Asset Tag: {:?}\n\ 10 | Board Name: {:?}\n\ 11 | Board Serial: {:?}\n\ 12 | Board Vendor: {:?}\n\ 13 | Board Version: {:?}\n\ 14 | Chassis Asset Tag: {:?}\n\ 15 | Chassis Name: {:?}\n\ 16 | Chassis Serial: {:?}\n\ 17 | Chassis Vendor: {:?}\n\ 18 | Chassis Version: {:?}\n\ 19 | Product Name: {:?}\n\ 20 | Product Serial: {:?}\n\ 21 | Product SKU: {:?}\n\ 22 | Product UUID: {:?}\n\ 23 | Product Version: {:?}\n\ 24 | Sys Vendor: {:?}", 25 | dmi.bios_date(), 26 | dmi.bios_vendor(), 27 | dmi.bios_version(), 28 | dmi.board_asset_tag(), 29 | dmi.board_name(), 30 | dmi.board_serial(), 31 | dmi.board_vendor(), 32 | dmi.board_version(), 33 | dmi.chassis_asset_tag(), 34 | dmi.chassis_name(), 35 | dmi.chassis_serial(), 36 | dmi.chassis_vendor(), 37 | dmi.chassis_version(), 38 | dmi.product_name(), 39 | dmi.product_serial(), 40 | dmi.product_sku(), 41 | dmi.product_uuid(), 42 | dmi.product_version(), 43 | dmi.sys_vendor() 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /examples/leds.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use sysfs_class::{Brightness, Leds, SysClass}; 3 | 4 | fn main() -> io::Result<()> { 5 | for dev in Leds::all()? { 6 | println!( 7 | "{} brightness: {} / {}", 8 | dev.id(), 9 | dev.brightness().unwrap(), 10 | dev.max_brightness().unwrap() 11 | ); 12 | } 13 | 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /examples/net.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use sysfs_class::{Net, SysClass}; 3 | 4 | fn main() -> io::Result<()> { 5 | for dev in Net::iter() { 6 | let dev = dev?; 7 | 8 | println!("{}: {}", dev.id(), dev.address().unwrap()); 9 | println!(" MTU: {}", dev.mtu().unwrap()); 10 | println!(" Duplex: {:?}", dev.duplex()); 11 | 12 | let statistics = dev.statistics(); 13 | println!( 14 | " RX: {} MiB", 15 | statistics.rx_bytes().unwrap() / (1024 * 1024) 16 | ); 17 | println!( 18 | " TX: {} MiB", 19 | statistics.tx_bytes().unwrap() / (1024 * 1024) 20 | ); 21 | } 22 | 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/pci.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use sysfs_class::{PciDevice, PciDriver, SysClass}; 3 | 4 | fn main() -> io::Result<()> { 5 | for dev in PciDevice::all()? { 6 | println!("PCI Device: {}", dev.id()); 7 | } 8 | 9 | for dev in PciDriver::iter() { 10 | let dev = dev?; 11 | println!("PCI Driver: {}", dev.id()); 12 | } 13 | 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /examples/scsi_host.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use sysfs_class::{ScsiHost, SysClass}; 3 | 4 | fn main() -> io::Result<()> { 5 | for host in ScsiHost::all()? { 6 | println!( 7 | "{} has power management policy \"{:?}\"", 8 | host.id(), 9 | host.link_power_management_policy() 10 | ); 11 | } 12 | 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /src/backlight.rs: -------------------------------------------------------------------------------- 1 | use crate::{Brightness, SysClass}; 2 | use std::io::Result; 3 | use std::path::{Path, PathBuf}; 4 | 5 | /// Fetch and modify brightness values of backlight controls. 6 | #[derive(Clone)] 7 | pub struct Backlight { 8 | path: PathBuf, 9 | } 10 | 11 | impl SysClass for Backlight { 12 | fn class() -> &'static str { 13 | "backlight" 14 | } 15 | 16 | unsafe fn from_path_unchecked(path: PathBuf) -> Self { 17 | Self { path } 18 | } 19 | 20 | fn path(&self) -> &Path { 21 | &self.path 22 | } 23 | } 24 | 25 | impl Backlight { 26 | method!(actual_brightness parse_file u64); 27 | 28 | method!(bl_power parse_file u64); 29 | 30 | method!("type", type_ trim_file String); 31 | } 32 | 33 | impl Brightness for Backlight {} 34 | -------------------------------------------------------------------------------- /src/block.rs: -------------------------------------------------------------------------------- 1 | use crate::SysClass; 2 | use std::io::Result; 3 | use std::path::{Path, PathBuf}; 4 | 5 | pub type SlaveIter = Box>>; 6 | 7 | /// A block device in /sys/class/block 8 | #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] 9 | pub struct Block { 10 | path: PathBuf, 11 | } 12 | 13 | impl SysClass for Block { 14 | fn class() -> &'static str { 15 | "block" 16 | } 17 | 18 | unsafe fn from_path_unchecked(path: PathBuf) -> Self { 19 | Self { path } 20 | } 21 | 22 | fn path(&self) -> &Path { 23 | &self.path 24 | } 25 | } 26 | 27 | impl Block { 28 | pub fn has_device(&self) -> bool { 29 | self.path.join("device").exists() 30 | } 31 | 32 | pub fn children(&self) -> Result> { 33 | let mut children = Block::all()? 34 | .into_iter() 35 | .filter(|x| { 36 | x.parent_device() 37 | .map_or(false, |parent| parent.path() == self.path) 38 | }) 39 | .collect::>(); 40 | children.sort_unstable(); 41 | Ok(children) 42 | } 43 | 44 | pub fn parent_device(&self) -> Option { 45 | self.partition().ok().and_then(|partition| { 46 | let path = self.path().to_str()?; 47 | let pos = path.len() - partition as usize / 10 - 1; 48 | let path = Path::new(path.split_at(pos).0).to_path_buf(); 49 | Some(unsafe { Block::from_path_unchecked(path) }) 50 | }) 51 | } 52 | 53 | /// Logical devices have their parent device(s) listed here. 54 | /// 55 | /// For example: 56 | /// 57 | /// - dm-4 has a slave of dm-0 58 | /// - dm-0 has a slave of sda3 59 | /// - sda3 does not have any slaves 60 | pub fn slaves(&self) -> Option> { 61 | let slaves_path = self.path.join("slaves"); 62 | if slaves_path.exists() { 63 | let res: Result = match slaves_path.read_dir() { 64 | Ok(iter) => Ok(Box::new(iter.map(|entry| Ok(entry?.path())))), 65 | Err(why) => Err(why), 66 | }; 67 | 68 | Some(res) 69 | } else { 70 | None 71 | } 72 | } 73 | 74 | // Base properties 75 | 76 | method!(alignment_offset parse_file u64); 77 | 78 | method!(capability parse_file u8); 79 | 80 | method!(dev read_file String); 81 | 82 | method!(discard_alignment parse_file u64); 83 | 84 | method!(events parse_file u64); 85 | 86 | method!(events_async parse_file u64); 87 | 88 | method!(events_poll_msecs parse_file u64); 89 | 90 | method!(ext_range parse_file u64); 91 | 92 | method!(hidden parse_file u8); 93 | 94 | method!(inflight read_file String); 95 | 96 | method!(partition parse_file u8); 97 | 98 | method!(range parse_file u64); 99 | 100 | method!(removable parse_file u8); 101 | 102 | method!(ro parse_file u8); 103 | 104 | method!(size parse_file u64); 105 | 106 | method!(start parse_file u64); 107 | 108 | method!(stat parse_file u8); 109 | 110 | method!(subsystem parse_file u8); 111 | 112 | method!(uevent read_file String); 113 | 114 | // bdi 115 | 116 | // device 117 | 118 | method!("device/device_blocked", device_blocked parse_file u8); 119 | 120 | method!("device/device_busy", device_busy parse_file u8); 121 | 122 | method!("device/model", device_model read_file String); 123 | 124 | method!("device/rev", device_rev read_file String); 125 | 126 | method!("device/state", device_state read_file String); 127 | 128 | method!("device/vendor", device_vendor read_file String); 129 | 130 | // holders 131 | 132 | // integrity 133 | 134 | // power 135 | 136 | // trace 137 | 138 | // queue 139 | 140 | method!("queue/add_random", queue_add_random parse_file u64); 141 | 142 | method!("queue/chunk_sectors", queue_chunk_sectors parse_file u64); 143 | 144 | method!("queue/dax", queue_dax parse_file u64); 145 | 146 | method!("queue/discard_granularity", queue_discard_granularity parse_file u64); 147 | 148 | method!("queue/discard_max_bytes", queue_discard_max_bytes parse_file u64); 149 | 150 | method!("queue/discard_max_hw_bytes", queue_discard_max_hw_bytes parse_file u64); 151 | 152 | method!("queue/discard_zeroes_data", queue_discard_zeroes_data parse_file u64); 153 | 154 | method!("queue/fua", queue_fua parse_file u64); 155 | 156 | method!("queue/hw_sector_size", queue_hw_sector_size parse_file u64); 157 | 158 | method!("queue/io_poll", queue_io_poll parse_file u64); 159 | 160 | method!("queue/io_poll_delay", queue_io_poll_delay parse_file u64); 161 | 162 | method!("queue/iostats", queue_iostats parse_file u64); 163 | 164 | method!("queue/logical_block_size", queue_logical_block_size parse_file u64); 165 | 166 | method!("queue/max_discard_segments", queue_max_discard_segments parse_file u64); 167 | 168 | method!("queue/max_hw_sectors_kb", queue_max_hw_sectors_kb parse_file u64); 169 | 170 | method!("queue/max_integrity_segments", queue_max_integrity_segments parse_file u64); 171 | 172 | method!("queue/max_sectors_kb", queue_max_sectors_kb parse_file u64); 173 | 174 | method!("queue/max_segment_size", queue_max_segment_size parse_file u64); 175 | 176 | method!("queue/max_segments", queue_max_segments parse_file u64); 177 | 178 | method!("queue/minimum_io_size", queue_minimum_io_size parse_file u64); 179 | 180 | method!("queue/nomerges", queue_nomerges parse_file u64); 181 | 182 | method!("queue/nr_requests", queue_nr_requests parse_file u64); 183 | 184 | method!("queue/optimal_io_size", queue_optimal_io_size parse_file u64); 185 | 186 | method!("queue/physical_block_size", queue_physical_block_size parse_file u64); 187 | 188 | method!("queue/read_ahead_kb", queue_read_ahead_kb parse_file u64); 189 | 190 | method!("queue/rotational", queue_rotational parse_file u8); 191 | 192 | method!("queue/rq_affinity", queue_rq_affinity parse_file u64); 193 | 194 | // method!("queue/scheduler", queue_scheduler parse_file u64); 195 | pub fn queue_scheduler(&self) -> Result { 196 | let mut active = 0; 197 | let mut schedules = Vec::new(); 198 | for schedule in self.read_file("queue/scheduler")?.split_whitespace() { 199 | let schedule = if schedule.starts_with('[') { 200 | active = schedules.len(); 201 | &schedule[1..schedule.len() - 1] 202 | } else { 203 | schedule 204 | }; 205 | 206 | schedules.push(schedule.to_owned()); 207 | } 208 | 209 | Ok(BlockScheduler { 210 | active: active as u8, 211 | schedules, 212 | }) 213 | } 214 | 215 | method!("queue/write_cache", queue_write_cache read_file String); 216 | 217 | method!("queue/write_same_max_bytes", queue_write_same_max_bytes parse_file u64); 218 | 219 | method!("queue/write_zeroes_max_bytes", queue_write_zeroes_max_bytes parse_file u64); 220 | 221 | method!("queue/zoned", queue_zoned read_file String); 222 | 223 | // queue/iosched 224 | 225 | method!("queue/iosched/back_seek_max", queue_iosched_back_seek_max parse_file u64); 226 | 227 | method!("queue/iosched/back_seek_penalty", queue_iosched_back_seek_penalty parse_file u64); 228 | 229 | method!("queue/iosched/fifo_expire_async", queue_iosched_fifo_expire_async parse_file u64); 230 | 231 | method!("queue/iosched/fifo_expire_sync", queue_iosched_fifo_expire_sync parse_file u64); 232 | 233 | method!("queue/iosched/group_idle", queue_iosched_group_idle parse_file u64); 234 | 235 | method!("queue/iosched/group_idle_us", queue_iosched_group_idle_us parse_file u64); 236 | 237 | method!("queue/iosched/low_latency", queue_iosched_low_latency parse_file u8); 238 | 239 | method!("queue/iosched/quantum", queue_iosched_quantum parse_file u64); 240 | 241 | method!("queue/iosched/slice_async", queue_iosched_slice_async parse_file u64); 242 | 243 | method!("queue/iosched/slice_async_rq", queue_iosched_slice_async_rq parse_file u64); 244 | 245 | method!("queue/iosched/slice_async_us", queue_iosched_slice_async_us parse_file u64); 246 | 247 | method!("queue/iosched/slice_idle", queue_iosched_slice_idle parse_file u8); 248 | 249 | method!("queue/iosched/slice_idle_us", queue_iosched_slice_idle_us parse_file u64); 250 | 251 | method!("queue/iosched/slice_sync", queue_iosched_slice_sync parse_file u64); 252 | 253 | method!("queue/iosched/slice_sync_us", queue_iosched_slice_sync_us parse_file u64); 254 | 255 | method!("queue/iosched/target_latency", queue_iosched_target_latency parse_file u64); 256 | 257 | method!("queue/iosched/target_latency_us", queue_iosched_target_latency_us parse_file u64); 258 | } 259 | 260 | pub struct BlockScheduler { 261 | schedules: Vec, 262 | active: u8, 263 | } 264 | 265 | impl BlockScheduler { 266 | pub fn active(&self) -> &str { 267 | &self.schedules[self.active as usize] 268 | } 269 | 270 | pub fn schedulers(&self) -> &[String] { 271 | &self.schedules 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/brightness.rs: -------------------------------------------------------------------------------- 1 | use crate::SysClass; 2 | use std::io::Result; 3 | 4 | pub trait Brightness: SysClass { 5 | trait_method!(brightness parse_file u64); 6 | 7 | trait_method!(max_brightness parse_file u64); 8 | 9 | set_trait_method!("brightness", set_brightness u64); 10 | 11 | /// Sets the `new` brightness level if it is less than the current brightness. 12 | /// 13 | /// Returns the brightness level that was set at the time of exiting the function. 14 | fn set_if_lower_than(&self, percent: u64) -> Result<()> { 15 | let max_brightness = self.max_brightness()?; 16 | let current = self.brightness()?; 17 | 18 | let new = max_brightness * percent / 100; 19 | if new < current { 20 | self.set_brightness(new) 21 | } else { 22 | Ok(()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/dmi.rs: -------------------------------------------------------------------------------- 1 | use crate::SysClass; 2 | use std::io::Result; 3 | use std::path::{Path, PathBuf}; 4 | 5 | const BASE_PATH: &str = "/sys/class/dmi/id"; 6 | 7 | /// Provides BIOS, Board, Chassis, Product, & Vendor identifiers 8 | #[derive(Clone)] 9 | pub struct DmiId { 10 | path: &'static Path, 11 | } 12 | 13 | impl Default for DmiId { 14 | fn default() -> Self { 15 | Self { 16 | path: Path::new(BASE_PATH), 17 | } 18 | } 19 | } 20 | 21 | impl SysClass for DmiId { 22 | fn class() -> &'static str { 23 | "dmi/id" 24 | } 25 | 26 | unsafe fn from_path_unchecked(_path: PathBuf) -> Self { 27 | Self::default() 28 | } 29 | 30 | fn path(&self) -> &Path { 31 | self.path 32 | } 33 | } 34 | 35 | impl DmiId { 36 | method!(bios_date read_file String); 37 | 38 | method!(bios_vendor read_file String); 39 | 40 | method!(bios_version read_file String); 41 | 42 | method!(board_asset_tag read_file String); 43 | 44 | method!(board_name read_file String); 45 | 46 | method!(board_serial read_file String); 47 | 48 | method!(board_vendor read_file String); 49 | 50 | method!(board_version read_file String); 51 | 52 | method!(chassis_asset_tag read_file String); 53 | 54 | method!(chassis_name read_file String); 55 | 56 | method!(chassis_serial read_file String); 57 | 58 | method!(chassis_vendor read_file String); 59 | 60 | method!(chassis_version read_file String); 61 | 62 | method!(modalias read_file String); 63 | 64 | method!(product_family read_file String); 65 | 66 | method!(product_name read_file String); 67 | 68 | method!(product_serial read_file String); 69 | 70 | method!(product_sku read_file String); 71 | 72 | method!(product_uuid read_file String); 73 | 74 | method!(product_version read_file String); 75 | 76 | method!(sys_vendor read_file String); 77 | } 78 | -------------------------------------------------------------------------------- /src/hwmon/fan.rs: -------------------------------------------------------------------------------- 1 | use crate::{HwMon, SysClass}; 2 | use std::io::Result; 3 | 4 | pub struct HwMonFan<'a> { 5 | hwmon: &'a HwMon, 6 | id: u64, 7 | } 8 | 9 | impl<'a> HwMonFan<'a> { 10 | pub fn new(hwmon: &'a HwMon, id: u64) -> Result { 11 | let s = Self { hwmon, id }; 12 | 13 | s.input()?; 14 | 15 | Ok(s) 16 | } 17 | 18 | pub fn label(&self) -> Result { 19 | self.hwmon.trim_file(&format!("fan{}_label", self.id)) 20 | } 21 | 22 | pub fn input(&self) -> Result { 23 | self.hwmon.parse_file(&format!("fan{}_input", self.id)) 24 | } 25 | 26 | pub fn min(&self) -> Result { 27 | self.hwmon.parse_file(&format!("fan{}_min", self.id)) 28 | } 29 | 30 | pub fn max(&self) -> Result { 31 | self.hwmon.parse_file(&format!("fan{}_max", self.id)) 32 | } 33 | 34 | pub fn target(&self) -> Result { 35 | self.hwmon.parse_file(&format!("fan{}_target", self.id)) 36 | } 37 | 38 | pub fn div(&self) -> Result { 39 | self.hwmon.parse_file(&format!("fan{}_div", self.id)) 40 | } 41 | 42 | pub fn pulses(&self) -> Result { 43 | self.hwmon.parse_file(&format!("fan{}_pulses", self.id)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/hwmon/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::SysClass; 2 | use std::io::Result; 3 | use std::path::{Path, PathBuf}; 4 | 5 | pub use self::fan::HwMonFan; 6 | mod fan; 7 | 8 | pub use self::pwm::HwMonPwm; 9 | mod pwm; 10 | 11 | pub use self::temp::HwMonTemp; 12 | mod temp; 13 | 14 | /// A hardware monitoring device in /sys/class/hwmon 15 | #[derive(Clone)] 16 | pub struct HwMon { 17 | path: PathBuf, 18 | } 19 | 20 | impl SysClass for HwMon { 21 | fn class() -> &'static str { 22 | "hwmon" 23 | } 24 | 25 | unsafe fn from_path_unchecked(path: PathBuf) -> Self { 26 | Self { path } 27 | } 28 | 29 | fn path(&self) -> &Path { 30 | &self.path 31 | } 32 | } 33 | 34 | impl HwMon { 35 | pub fn name(&self) -> Result { 36 | self.trim_file("name") 37 | } 38 | 39 | pub fn fan<'a>(&'a self, id: u64) -> Result> { 40 | HwMonFan::new(self, id) 41 | } 42 | 43 | pub fn pwm<'a>(&'a self, id: u64) -> Result> { 44 | HwMonPwm::new(self, id) 45 | } 46 | 47 | pub fn temp<'a>(&'a self, id: u64) -> Result> { 48 | HwMonTemp::new(self, id) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/hwmon/pwm.rs: -------------------------------------------------------------------------------- 1 | use crate::{HwMon, SysClass}; 2 | use std::io::Result; 3 | 4 | pub struct HwMonPwm<'a> { 5 | hwmon: &'a HwMon, 6 | id: u64, 7 | } 8 | 9 | impl<'a> HwMonPwm<'a> { 10 | pub fn new(hwmon: &'a HwMon, id: u64) -> Result { 11 | let s = Self { hwmon, id }; 12 | 13 | s.input()?; 14 | 15 | Ok(s) 16 | } 17 | 18 | pub fn input(&self) -> Result { 19 | self.hwmon.parse_file(&format!("pwm{}", self.id)) 20 | } 21 | 22 | pub fn min(&self) -> Result { 23 | self.hwmon.parse_file(&format!("pwm{}_min", self.id)) 24 | } 25 | 26 | pub fn max(&self) -> Result { 27 | self.hwmon.parse_file(&format!("pwm{}_max", self.id)) 28 | } 29 | 30 | pub fn freq(&self) -> Result { 31 | self.hwmon.parse_file(&format!("pwm{}_freq", self.id)) 32 | } 33 | 34 | pub fn enable(&self) -> Result { 35 | self.hwmon.parse_file(&format!("pwm{}_enable", self.id)) 36 | } 37 | 38 | pub fn mode(&self) -> Result { 39 | self.hwmon.parse_file(&format!("pwm{}_mode", self.id)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/hwmon/temp.rs: -------------------------------------------------------------------------------- 1 | use crate::{HwMon, SysClass}; 2 | use std::io::Result; 3 | 4 | pub struct HwMonTemp<'a> { 5 | hwmon: &'a HwMon, 6 | id: u64, 7 | } 8 | 9 | impl<'a> HwMonTemp<'a> { 10 | pub fn new(hwmon: &'a HwMon, id: u64) -> Result { 11 | let s = Self { hwmon, id }; 12 | 13 | s.input()?; 14 | 15 | Ok(s) 16 | } 17 | 18 | pub fn label(&self) -> Result { 19 | self.hwmon.trim_file(&format!("temp{}_label", self.id)) 20 | } 21 | 22 | pub fn input(&self) -> Result { 23 | self.hwmon.parse_file(&format!("temp{}_input", self.id)) 24 | } 25 | 26 | pub fn lcrit(&self) -> Result { 27 | self.hwmon.parse_file(&format!("temp{}_lcrit", self.id)) 28 | } 29 | 30 | pub fn min(&self) -> Result { 31 | self.hwmon.parse_file(&format!("temp{}_min", self.id)) 32 | } 33 | 34 | pub fn max(&self) -> Result { 35 | self.hwmon.parse_file(&format!("temp{}_max", self.id)) 36 | } 37 | 38 | pub fn crit(&self) -> Result { 39 | self.hwmon.parse_file(&format!("temp{}_crit", self.id)) 40 | } 41 | 42 | pub fn emergency(&self) -> Result { 43 | self.hwmon.parse_file(&format!("temp{}_emergency", self.id)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/leds.rs: -------------------------------------------------------------------------------- 1 | use crate::{Brightness, SysClass}; 2 | use std::io::Result; 3 | use std::path::{Path, PathBuf}; 4 | 5 | /// Fetch and modify brightness values of LED controllers. 6 | #[derive(Clone)] 7 | pub struct Leds { 8 | path: PathBuf, 9 | } 10 | 11 | impl SysClass for Leds { 12 | fn class() -> &'static str { 13 | "leds" 14 | } 15 | 16 | unsafe fn from_path_unchecked(path: PathBuf) -> Self { 17 | Self { path } 18 | } 19 | 20 | fn path(&self) -> &Path { 21 | &self.path 22 | } 23 | } 24 | 25 | impl Leds { 26 | /// Filters backlights to only include keyboard backlights 27 | pub fn iter_keyboards() -> impl Iterator> 28 | where 29 | Self: 'static, 30 | { 31 | Self::iter().filter(move |object| { 32 | object 33 | .as_ref() 34 | .ok() 35 | .map_or(true, |o| o.id().contains("kbd_backlight")) 36 | }) 37 | } 38 | } 39 | 40 | impl Brightness for Leds {} 41 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use crate::sys_class::SysClass; 2 | #[macro_use] 3 | mod sys_class; 4 | 5 | pub use crate::backlight::Backlight; 6 | mod backlight; 7 | 8 | pub use crate::brightness::Brightness; 9 | mod brightness; 10 | 11 | pub use crate::block::Block; 12 | mod block; 13 | 14 | pub use crate::dmi::DmiId; 15 | mod dmi; 16 | 17 | pub use crate::hwmon::{HwMon, HwMonFan, HwMonPwm, HwMonTemp}; 18 | mod hwmon; 19 | 20 | pub use crate::leds::Leds; 21 | mod leds; 22 | 23 | pub use crate::net::Net; 24 | mod net; 25 | 26 | pub use crate::pci_bus::{PciDevice, PciDriver}; 27 | mod pci_bus; 28 | 29 | pub use crate::runtime_pm::{RuntimePM, RuntimePowerManagement}; 30 | mod runtime_pm; 31 | 32 | pub use crate::scsi_host::ScsiHost; 33 | mod scsi_host; 34 | -------------------------------------------------------------------------------- /src/net.rs: -------------------------------------------------------------------------------- 1 | use crate::SysClass; 2 | use std::io::Result; 3 | use std::path::{Path, PathBuf}; 4 | 5 | #[derive(Clone)] 6 | pub struct Net { 7 | path: PathBuf, 8 | } 9 | 10 | impl SysClass for Net { 11 | fn class() -> &'static str { 12 | "net" 13 | } 14 | 15 | unsafe fn from_path_unchecked(path: PathBuf) -> Self { 16 | Self { path } 17 | } 18 | 19 | fn path(&self) -> &Path { 20 | &self.path 21 | } 22 | } 23 | 24 | impl Net { 25 | pub fn statistics(&self) -> NetStatistics { 26 | NetStatistics { parent: self } 27 | } 28 | 29 | method!(addr_assign_type parse_file u8); 30 | method!(addr_len parse_file u16); 31 | method!(address trim_file String); 32 | method!(broadcast trim_file String); 33 | method!(carrier parse_file u16); 34 | method!(carrier_changes parse_file u16); 35 | method!(carrier_down_count parse_file u16); 36 | method!(carrier_up_count parse_file u16); 37 | method!(dev_id trim_file String); 38 | method!(dev_port parse_file u16); 39 | method!(dormant parse_file u8); 40 | method!(duplex trim_file String); 41 | method!(mtu parse_file u32); 42 | method!(operstate trim_file String); 43 | method!(speed parse_file u32); 44 | method!(tx_queue_len parse_file u32); 45 | } 46 | 47 | pub struct NetStatistics<'a> { 48 | parent: &'a Net, 49 | } 50 | 51 | impl<'a> NetStatistics<'a> { 52 | const DIR: &'static str = "statistics"; 53 | 54 | pub fn rx_bytes(&self) -> Result { 55 | self.parent.parse_file(&[Self::DIR, "/rx_bytes"].concat()) 56 | } 57 | 58 | pub fn rx_packets(&self) -> Result { 59 | self.parent.parse_file(&[Self::DIR, "/rx_packets"].concat()) 60 | } 61 | 62 | pub fn tx_bytes(&self) -> Result { 63 | self.parent.parse_file(&[Self::DIR, "/tx_bytes"].concat()) 64 | } 65 | 66 | pub fn tx_packets(&self) -> Result { 67 | self.parent.parse_file(&[Self::DIR, "/tx_packets"].concat()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/pci_bus/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{RuntimePM, RuntimePowerManagement, SysClass}; 2 | use std::fs; 3 | use std::io; 4 | use std::path::{Path, PathBuf}; 5 | 6 | #[derive(Clone)] 7 | pub struct PciDriver { 8 | path: PathBuf, 9 | } 10 | 11 | impl SysClass for PciDriver { 12 | fn base() -> &'static str { 13 | "bus" 14 | } 15 | 16 | fn class() -> &'static str { 17 | "pci/drivers" 18 | } 19 | 20 | unsafe fn from_path_unchecked(path: PathBuf) -> Self { 21 | Self { path } 22 | } 23 | 24 | fn path(&self) -> &Path { 25 | &self.path 26 | } 27 | } 28 | 29 | impl PciDriver { 30 | pub unsafe fn bind(&self, device: &PciDevice) -> io::Result<()> { 31 | self.write_file("bind", device.id()) 32 | } 33 | 34 | pub unsafe fn unbind(&self, device: &PciDevice) -> io::Result<()> { 35 | self.write_file("unbind", device.id()) 36 | } 37 | } 38 | 39 | macro_rules! pci_devices { 40 | ($( fn $file:tt -> $out:tt; )*) => { 41 | $( 42 | pub fn $file(&self) -> io::Result<$out> { 43 | let v = self.read_file(stringify!($file))?; 44 | $out::from_str_radix(v[2..].trim(), 16).map_err(|err| { 45 | io::Error::new( 46 | io::ErrorKind::InvalidData, 47 | format!("{}", err) 48 | ) 49 | }) 50 | } 51 | )* 52 | } 53 | } 54 | 55 | #[derive(Clone)] 56 | pub struct PciDevice { 57 | path: PathBuf, 58 | } 59 | 60 | impl SysClass for PciDevice { 61 | fn base() -> &'static str { 62 | "bus" 63 | } 64 | 65 | fn class() -> &'static str { 66 | "pci/devices" 67 | } 68 | 69 | unsafe fn from_path_unchecked(path: PathBuf) -> Self { 70 | Self { path } 71 | } 72 | 73 | fn path(&self) -> &Path { 74 | &self.path 75 | } 76 | } 77 | 78 | impl PciDevice { 79 | pci_devices! { 80 | fn class -> u32; 81 | fn device -> u16; 82 | fn revision -> u8; 83 | fn subsystem_device -> u16; 84 | fn subsystem_vendor -> u16; 85 | fn vendor -> u16; 86 | } 87 | 88 | pub fn driver(&self) -> io::Result { 89 | fs::canonicalize(self.path.join("driver")).map(|path| PciDriver { path }) 90 | } 91 | 92 | pub unsafe fn remove(&self) -> io::Result<()> { 93 | self.write_file("remove", "1") 94 | } 95 | } 96 | 97 | impl RuntimePM for PciDevice { 98 | fn set_runtime_pm(&self, state: RuntimePowerManagement) -> io::Result<()> { 99 | self.write_file("power/control", <&'static str>::from(state)) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/runtime_pm.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | /// Control whether a device uses, or does not use, runtime power management. 4 | #[derive(Copy, Clone, Debug, PartialEq)] 5 | pub enum RuntimePowerManagement { 6 | On, 7 | Off, 8 | } 9 | 10 | impl From for &'static str { 11 | fn from(pm: RuntimePowerManagement) -> &'static str { 12 | match pm { 13 | RuntimePowerManagement::On => "auto", 14 | RuntimePowerManagement::Off => "on", 15 | } 16 | } 17 | } 18 | 19 | pub trait RuntimePM { 20 | fn set_runtime_pm(&self, state: RuntimePowerManagement) -> io::Result<()>; 21 | } 22 | -------------------------------------------------------------------------------- /src/scsi_host.rs: -------------------------------------------------------------------------------- 1 | use crate::SysClass; 2 | use std::io::{self, Result}; 3 | use std::path::{Path, PathBuf}; 4 | 5 | /// Fetch and modify SCSI host parameters. 6 | #[derive(Clone)] 7 | pub struct ScsiHost { 8 | path: PathBuf, 9 | } 10 | 11 | impl SysClass for ScsiHost { 12 | fn class() -> &'static str { 13 | "scsi_host" 14 | } 15 | 16 | unsafe fn from_path_unchecked(path: PathBuf) -> Self { 17 | Self { path } 18 | } 19 | 20 | fn path(&self) -> &Path { 21 | &self.path 22 | } 23 | } 24 | 25 | impl ScsiHost { 26 | method!(active_mod trim_file String); 27 | 28 | method!(can_queue parse_file i32); 29 | 30 | method!(host_busy parse_file u8); 31 | 32 | method!(link_power_management_policy trim_file String); 33 | 34 | /// Sets the power management profile for this SCSI host. 35 | /// 36 | /// Multiple profiles are given, and each profile is tried until one succeeds. 37 | pub fn set_link_power_management_policy<'b>( 38 | &self, 39 | profiles: &[&'b str], 40 | ) -> io::Result<&'b str> { 41 | debug_assert!( 42 | !profiles.is_empty(), 43 | "at least one profile must be specified" 44 | ); 45 | 46 | let mut last_result = Ok(()); 47 | let mut last_prof = ""; 48 | 49 | for prof in profiles { 50 | last_result = self.write_file("link_power_management_policy", prof); 51 | last_prof = prof; 52 | if last_result.is_ok() { 53 | break; 54 | } 55 | } 56 | 57 | last_result.map(|_| last_prof) 58 | } 59 | 60 | method!(proc_name trim_file String); 61 | 62 | method!(sg_tablesize parse_file i32); 63 | 64 | method!(state trim_file String); 65 | 66 | method!(supported_mode parse_file u8); 67 | 68 | method!(use_blk_mq parse_file u8); 69 | } 70 | -------------------------------------------------------------------------------- /src/sys_class.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::fs; 3 | use std::io::{Error, ErrorKind, Read, Result, Write}; 4 | use std::path::{Path, PathBuf}; 5 | use std::str::FromStr; 6 | 7 | #[macro_export] 8 | macro_rules! method { 9 | ($file:tt $with:tt $out:tt) => { 10 | pub fn $file(&self) -> Result<$out> { 11 | self.$with(stringify!($file)) 12 | } 13 | }; 14 | 15 | ($file:expr, $method:tt $with:tt $out:tt) => { 16 | pub fn $method(&self) -> Result<$out> { 17 | self.$with($file) 18 | } 19 | }; 20 | } 21 | 22 | #[macro_export] 23 | macro_rules! trait_method { 24 | ($file:tt $with:tt $out:tt) => { 25 | fn $file(&self) -> Result<$out> { 26 | self.$with(stringify!($file)) 27 | } 28 | }; 29 | 30 | ($file:expr, $method:tt $with:tt $out:tt) => { 31 | fn $method(&self) -> Result<$out> { 32 | self.$with($file) 33 | } 34 | }; 35 | } 36 | 37 | #[macro_export] 38 | macro_rules! set_method { 39 | ($file:expr, $method:tt $with:ty) => { 40 | pub fn $method(&self, input: $with) -> Result<()> { 41 | use numtoa::NumToA; 42 | let mut buf = [0u8; 20]; 43 | self.write_file($file, input.numtoa_str(10, &mut buf)) 44 | } 45 | }; 46 | 47 | ($file:expr, $method:tt) => { 48 | pub fn $method>(&self, input: B) -> Result<()> { 49 | self.write_file($file, input.as_ref()) 50 | } 51 | }; 52 | } 53 | 54 | #[macro_export] 55 | macro_rules! set_trait_method { 56 | ($file:expr, $method:tt $with:ty) => { 57 | fn $method(&self, input: $with) -> Result<()> { 58 | use numtoa::NumToA; 59 | let mut buf = [0u8; 20]; 60 | self.write_file($file, input.numtoa_str(10, &mut buf)) 61 | } 62 | }; 63 | 64 | ($file:expr, $method:tt) => { 65 | fn $method>(&self, input: B) -> Result<()> { 66 | self.write_file($file, input.as_ref()) 67 | } 68 | }; 69 | } 70 | 71 | pub trait SysClass: Sized { 72 | /// Sets the base directory, which defaults to `class`. 73 | fn base() -> &'static str { 74 | "class" 75 | } 76 | /// Return the class of the sys object, the name of a folder in `/sys/${base}`` 77 | fn class() -> &'static str; 78 | 79 | /// Create a sys object from an absolute path without checking path for validity 80 | unsafe fn from_path_unchecked(path: PathBuf) -> Self; 81 | 82 | /// Return the path of the sys object 83 | fn path(&self) -> &Path; 84 | 85 | /// Return the path to the sys objects, the full path of a folder in /sys/class 86 | fn dir() -> PathBuf { 87 | Path::new("/sys/").join(Self::base()).join(Self::class()) 88 | } 89 | 90 | /// Create a sys object from a path, checking it for validity 91 | fn from_path(path: &Path) -> Result { 92 | { 93 | let parent = path.parent().ok_or_else(|| { 94 | Error::new( 95 | ErrorKind::InvalidInput, 96 | format!("{}: does not have parent", path.display()), 97 | ) 98 | })?; 99 | 100 | let dir = Self::dir(); 101 | if parent != dir { 102 | return Err(Error::new( 103 | ErrorKind::InvalidInput, 104 | format!("{}: is not a child of {}", path.display(), dir.display()), 105 | )); 106 | } 107 | } 108 | 109 | fs::read_dir(&path)?; 110 | 111 | Ok(unsafe { Self::from_path_unchecked(path.to_owned()) }) 112 | } 113 | 114 | /// Retrieve all of the object instances of a sys class 115 | fn all() -> Result> { 116 | let mut ret = Vec::new(); 117 | 118 | for entry_res in fs::read_dir(Self::dir())? { 119 | let entry = entry_res?; 120 | ret.push(Self::from_path(&entry.path())?); 121 | } 122 | 123 | Ok(ret) 124 | } 125 | 126 | /// Retrieve all of the object instances of a sys class, with a boxed iterator 127 | fn iter() -> Box>> 128 | where 129 | Self: 'static, 130 | { 131 | match fs::read_dir(Self::dir()) { 132 | Ok(entries) => Box::new( 133 | entries.map(|entry_res| entry_res.and_then(|entry| Self::from_path(&entry.path()))), 134 | ), 135 | Err(why) => Box::new(::std::iter::once(Err(why))), 136 | } 137 | } 138 | 139 | /// Create a sys object by id, checking it for validity 140 | fn new(id: &str) -> Result { 141 | Self::from_path(&Self::dir().join(id)) 142 | } 143 | 144 | /// Return the id of the sys object 145 | fn id(&self) -> &str { 146 | self.path() 147 | .file_name() 148 | .unwrap() // A valid path does not end in .., so safe 149 | .to_str() 150 | .unwrap() // A valid path must be valid UTF-8, so safe 151 | } 152 | 153 | /// Read a file underneath the sys object 154 | fn read_file>(&self, name: P) -> Result { 155 | let mut data = String::new(); 156 | 157 | { 158 | let path = self.path().join(name.as_ref()); 159 | let mut file = fs::OpenOptions::new().read(true).open(path)?; 160 | file.read_to_string(&mut data)?; 161 | } 162 | 163 | Ok(data) 164 | } 165 | 166 | /// Parse a number from a file underneath the sys object 167 | fn parse_file>(&self, name: P) -> Result 168 | where 169 | F::Err: Display, 170 | { 171 | self.read_file(name.as_ref())? 172 | .trim() 173 | .parse() 174 | .map_err(|err| { 175 | Error::new( 176 | ErrorKind::InvalidData, 177 | format!("{}: {}", name.as_ref().display(), err), 178 | ) 179 | }) 180 | } 181 | 182 | /// Read a file underneath the sys object and trim whitespace 183 | fn trim_file>(&self, name: P) -> Result { 184 | let data = self.read_file(name)?; 185 | Ok(data.trim().to_string()) 186 | } 187 | 188 | /// Write a file underneath the sys object 189 | fn write_file, S: AsRef<[u8]>>(&self, name: P, data: S) -> Result<()> { 190 | { 191 | let path = self.path().join(name.as_ref()); 192 | let mut file = fs::OpenOptions::new().write(true).open(path)?; 193 | file.write_all(data.as_ref())? 194 | } 195 | 196 | Ok(()) 197 | } 198 | } 199 | --------------------------------------------------------------------------------