├── .cirrus.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── _config.yml ├── examples ├── iterate.rs ├── set_value.rs ├── struct.rs ├── temperature.rs ├── value.rs ├── value_as.rs ├── value_oid_as.rs └── value_string.rs └── src ├── consts.rs ├── ctl_error.rs ├── ctl_flags.rs ├── ctl_info.rs ├── ctl_type.rs ├── ctl_value.rs ├── lib.rs ├── linux ├── ctl.rs ├── ctl_iter.rs ├── funcs.rs └── mod.rs ├── temperature.rs ├── traits.rs └── unix ├── ctl.rs ├── ctl_iter.rs ├── funcs.rs └── mod.rs /.cirrus.yml: -------------------------------------------------------------------------------- 1 | test: &TEST 2 | cargo_cache: 3 | folder: $CARGO_HOME/registry 4 | build_script: 5 | - . $HOME/.cargo/env || true 6 | - cargo build --all --all-targets 7 | test_script: 8 | - . $HOME/.cargo/env || true 9 | - cargo test --all 10 | before_cache_script: rm -rf $CARGO_HOME/registry/index 11 | 12 | task: 13 | matrix: 14 | - name: FreeBSD 14 15 | freebsd_instance: 16 | image: freebsd-14-0-release-amd64-ufs 17 | - name: FreeBSD 13 18 | freebsd_instance: 19 | image: freebsd-13-3-release-amd64 20 | install_script: | 21 | fetch https://sh.rustup.rs -o rustup.sh 22 | sh rustup.sh -y --profile=minimal 23 | << : *TEST 24 | 25 | task: 26 | name: OSX/arm64 27 | macos_instance: 28 | image: ghcr.io/cirruslabs/macos-ventura-base:latest 29 | install_script: | 30 | curl https://sh.rustup.rs -sSf | sh -s -- -y 31 | << : *TEST 32 | 33 | task: 34 | name: Linux 35 | container: 36 | image: rust:latest 37 | << : *TEST 38 | target_cache: 39 | folder: target 40 | fingerprint_script: 41 | - rustc --version 42 | 43 | test_task: 44 | name: nightly 45 | depends_on: 46 | - FreeBSD 14 47 | - FreeBSD 13 48 | - OSX/arm64 49 | - Linux 50 | container: 51 | image: rustlang/rust:nightly 52 | cargo_cache: 53 | folder: $CARGO_HOME/registry 54 | build_script: cargo build --all 55 | test_script: cargo test --all 56 | target_cache: 57 | folder: target 58 | fingerprint_script: 59 | - rustc --version 60 | minver_test_script: 61 | - cargo update -Zdirect-minimal-versions 62 | - cargo check --all-targets 63 | before_cache_script: rm -rf $CARGO_HOME/registry/index 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | **/*.core 4 | 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [Unreleased] - ReleaseDate 5 | 6 | ### Changed 7 | 8 | ## [0.6.0] - 2024-09-10 9 | ### Changed 10 | - Added support for Apple tvOS and visionOS. 11 | 12 | ## [0.5.5] - 2023-10-31 13 | ### Changed 14 | - Updated enum-as-inner to 0.6.0, to remove dependencies on both syn-1 and syn-2. 15 | - Updated bitflags to minimum 2.0. 16 | 17 | ## [0.5.4] - 2022-12-09 18 | ### Changed 19 | - Bumped byteorder crate to 1.4.3 due to failing tests. 20 | 21 | ## [0.5.3] - 2022-12-09 22 | ### Added 23 | - Added accessor methods to destructure `CtlValue`. 24 | 25 | ## [0.5.2] - 2022-08-16 26 | ### Changed 27 | - CI minimum version test failing. Adjust versions to fix. 28 | 29 | ## [0.5.1] - 2022-08-16 30 | ### Changed 31 | - Remove crate version from example in readme. 32 | 33 | ## [0.5.0] - 2022-08-16 34 | ### Changed 35 | - Improve iOS support with new Ctl variant. 36 | - Increase minimum version of dependencies. 37 | 38 | ## [0.4.6] - 2022-08-07 39 | ### Changed 40 | - Can't have more than 5 keywords in Cargo.toml. Remove the added iOS keyword. 41 | 42 | ## [0.4.5] - 2022-08-07 43 | ### Changed 44 | - Enable use on iOS 45 | 46 | ## [0.4.4] - 2022-03-01 47 | ### Changed 48 | - Use fmt to determine the exact type for CtlType::Int on MacOS 49 | 50 | ## [0.4.3] - 2021-11-01 51 | ### Changed 52 | - Remove a leftover debug println. 53 | 54 | ## [0.4.2] - 2021-08-03 55 | ### Changed 56 | - Add Cirrus CI for FreeBSD, macOS and Linux. 57 | - Bump thiserror crate. 58 | - Use sysctlnametomib(3) where available. 59 | - Use sysctlbyname(3) on FreeBSD. 60 | - Tell docs.rs to build docs for FreeBSD too. 61 | - Don't include docs in package to reduce size. 62 | 63 | ## [0.4.1] - 2021-04-23 64 | ### Changed 65 | - Replace deprecated failure crate with thiserror. 66 | - Fix clippy lints. 67 | 68 | ## [0.4.0] - 2019-07-24 69 | ### Changed 70 | - Add Linux support. 71 | - Huge refactor. 72 | - Improve BSD code to provide a cross platform compatible API. 73 | - [BREAKING] Make static functions private, all calls now go through the Ctl object. 74 | 75 | ## [0.3.0] - 2019-01-07 76 | ### Changed 77 | - Improve error handling. 78 | - Publish CtlInfo struct. 79 | - Add Cirrus CI script. 80 | 81 | ## [0.2.0] - 2018-05-28 82 | ### Changed 83 | - Add iterator support (thanks to Fabian Freyer!). 84 | - Add struct interface for control. 85 | - Add documentation for macOS. 86 | - Use failure create for error handling. 87 | 88 | ## [0.1.4] - 2018-01-04 89 | ### Changed 90 | - Fix documentation link 91 | - Fix test on FreeBSD 92 | 93 | ## [0.1.3] - 2018-01-04 94 | ### Added 95 | - Macos support. 96 | 97 | ## [0.1.2] - 2017-05-23 98 | ### Added 99 | - This changelog. 100 | - API to get values by OID. 101 | - Example value\_oid\_as.rs 102 | - Node types can also contain data so treat Nodes same as Struct/Opaque. 103 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sysctl" 3 | version = "0.6.0" 4 | authors = [ 5 | "Johannes Lundberg ", 6 | "Ivan Temchenko ", 7 | "Fabian Freyer " 8 | ] 9 | description = "Simplified interface to libc::sysctl" 10 | keywords = ["sysctl", "sysfs", "freebsd", "macos", "linux"] 11 | license = "MIT" 12 | readme = "README.md" 13 | repository = "https://github.com/johalun/sysctl-rs" 14 | documentation = "https://docs.rs/sysctl" 15 | include = ["src/**/*", "LICENSE-MIT", "README.md", "CHANGELOG.md"] 16 | 17 | [package.metadata.docs.rs] 18 | targets = [ 19 | "i686-unknown-freebsd", 20 | "i686-unknown-linux-gnu", 21 | "x86_64-apple-darwin", 22 | "x86_64-unknown-freebsd", 23 | "x86_64-unknown-linux-gnu", 24 | ] 25 | 26 | [dependencies] 27 | enum-as-inner = "0.6.0" 28 | libc = "^0.2.34" 29 | byteorder = "^1.4.3" 30 | thiserror = "^1.0.32" 31 | bitflags = "^2" 32 | 33 | [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] 34 | walkdir = "^2.2.8" 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Johannes Lundberg 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This crate provides a safe interface for reading and writing information to the kernel using the sysctl interface. 2 | 3 | [![Build Status](https://api.cirrus-ci.com/github/johalun/sysctl-rs.svg)](https://cirrus-ci.com/github/johalun/sysctl-rs/master) 4 | 5 | [![Current Version](https://img.shields.io/crates/v/sysctl.svg)](https://crates.io/crates/sysctl) 6 | 7 | 8 | *FreeBSD, Linux, macOS, iOS, tvOS, and visionOS are supported.* 9 | *Contributions for improvements and other platforms are welcome.* 10 | 11 | ### Documentation 12 | 13 | Documentation is available on [docs.rs](https://docs.rs/sysctl) 14 | 15 | ### Usage 16 | 17 | Add to `Cargo.toml` 18 | 19 | ```toml 20 | [dependencies] 21 | sysctl = "*" 22 | ``` 23 | 24 | ### macOS/iOS 25 | 26 | * Due to limitations in the sysctl(3) API, many of the methods of 27 | the `Ctl` take a mutable reference to `self` on macOS/iOS. 28 | * Sysctl descriptions are not available on macOS/iOS and Linux. 29 | * Some tests failures are ignored, as the respective sysctls do not 30 | exist on macos. 31 | 32 | ### Example 33 | 34 | sysctl comes with several examples, see the examples folder: 35 | 36 | * `value.rs`: shows how to get a sysctl value 37 | * `value_as.rs`: parsing values as structures 38 | * `value_string.rs`: parsing values as string. Use this for cross platform compatibility since all sysctls are strings on Linux. 39 | * `value_oid_as.rs`: getting a sysctl from OID constants from the `libc` crate. 40 | * `set_value.rs`: shows how to set a sysctl value 41 | * `struct.rs`: reading data into a struct 42 | * `temperature.rs`: parsing temperatures 43 | * `iterate.rs`: showcases iteration over the sysctl tree 44 | 45 | Run with: 46 | 47 | ```sh 48 | $ cargo run --example iterate 49 | ``` 50 | 51 | Or to use in your program: 52 | 53 | ```rust 54 | extern crate sysctl; 55 | use sysctl::Sysctl; 56 | 57 | fn main() { 58 | let ctl = sysctl::Ctl::new("kern.osrevision").unwrap(); 59 | println!("Description: {}", ctl.description().unwrap()); 60 | println!("Value: {}", ctl.value_string().unwrap()); 61 | } 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /examples/iterate.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | extern crate sysctl; 5 | 6 | // Import the trait 7 | use sysctl::Sysctl; 8 | 9 | fn print_ctl(ctl: &sysctl::Ctl) { 10 | let name = ctl.name().expect("Could not get name of control"); 11 | 12 | if let Ok(value) = ctl.value() { 13 | println!("{}: {}", name, value); 14 | } 15 | } 16 | 17 | fn main() { 18 | let args: Vec<_> = std::env::args().collect(); 19 | 20 | let ctls = match args.len() { 21 | 1 => sysctl::CtlIter::root().filter_map(Result::ok), 22 | 2 => { 23 | let root = sysctl::Ctl::new(&args[1]).expect("Could not get given root node."); 24 | 25 | let value_type = root 26 | .value_type() 27 | .expect("Could not get value type of given sysctl"); 28 | if value_type != sysctl::CtlType::Node { 29 | print_ctl(&root); 30 | return; 31 | } 32 | 33 | root.into_iter().filter_map(Result::ok) 34 | } 35 | _ => panic!("More than 1 command-line argument given"), 36 | }; 37 | 38 | for ctl in ctls { 39 | let flags = match ctl.flags() { 40 | Ok(f) => f, 41 | Err(_) => continue, 42 | }; 43 | 44 | if !flags.contains(sysctl::CtlFlags::SKIP) { 45 | print_ctl(&ctl); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/set_value.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | extern crate libc; 5 | extern crate sysctl; 6 | 7 | // Import the trait 8 | use sysctl::Sysctl; 9 | 10 | #[cfg(target_os = "freebsd")] 11 | const CTLNAME: &str = "net.inet.ip.forwarding"; 12 | 13 | #[cfg(target_os = "macos")] 14 | const CTLNAME: &str = "net.inet.ip.forwarding"; 15 | 16 | #[cfg(any(target_os = "linux", target_os = "android"))] 17 | const CTLNAME: &str = "net.ipv4.ip_forward"; 18 | 19 | fn main() { 20 | assert_eq!( 21 | unsafe { libc::geteuid() }, 22 | 0, 23 | "This example must be run as root" 24 | ); 25 | 26 | let ctl = sysctl::Ctl::new(CTLNAME).expect(&format!("could not get sysctl '{}'", CTLNAME)); 27 | 28 | let name = ctl.name().expect("could not get sysctl name"); 29 | println!("\nFlipping value of sysctl '{}'", name); 30 | 31 | let old_value = ctl.value_string().expect("could not get sysctl value"); 32 | println!("Current value is '{}'", old_value); 33 | 34 | let target_value = match old_value.as_ref() { 35 | "0" => "1", 36 | _ => "0", 37 | }; 38 | 39 | println!("Setting value to '{}'...", target_value); 40 | let new_value = ctl.set_value_string(target_value).unwrap_or_else(|e| { 41 | panic!("Could not set value. Error: {:?}", e); 42 | }); 43 | assert_eq!(new_value, target_value, "could not set value"); 44 | println!("OK. Now restoring old value '{}'...", old_value); 45 | 46 | let ret = ctl 47 | .set_value_string(&old_value) 48 | .expect("could not restore old value"); 49 | println!("OK. Value restored to {}.", ret); 50 | } 51 | -------------------------------------------------------------------------------- /examples/struct.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | extern crate libc; 5 | extern crate sysctl; 6 | 7 | // Import the trait 8 | use sysctl::Sysctl; 9 | 10 | // Converted from definition in from /usr/include/sys/time.h 11 | #[derive(Debug)] 12 | #[repr(C)] 13 | struct ClockInfo { 14 | hz: libc::c_int, /* clock frequency */ 15 | tick: libc::c_int, /* micro-seconds per hz tick */ 16 | spare: libc::c_int, 17 | stathz: libc::c_int, /* statistics clock frequency */ 18 | profhz: libc::c_int, /* profiling clock frequency */ 19 | } 20 | 21 | #[cfg(any(target_os = "freebsd", target_os = "macos"))] 22 | fn main() { 23 | let ctl = sysctl::Ctl::new("kern.clockrate").expect("could not get sysctl: kern.clockrate"); 24 | 25 | let name = ctl.name().expect("could not get sysctl name"); 26 | println!("Read sysctl {} and parse result to struct ClockInfo", name); 27 | 28 | let d = ctl.description().expect("could not get sysctl description"); 29 | println!("Description: {:?}", d); 30 | 31 | if let Ok(s) = ctl.value_as::() { 32 | println!("{:?}", s); 33 | } 34 | } 35 | 36 | #[cfg(not(any(target_os = "freebsd", target_os = "macos")))] 37 | fn main() { 38 | println!("This operation is only supported on FreeBSD and macOS."); 39 | } 40 | -------------------------------------------------------------------------------- /examples/temperature.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | extern crate sysctl; 5 | 6 | // Import the trait 7 | use sysctl::Sysctl; 8 | 9 | #[cfg(target_os = "freebsd")] 10 | fn main() { 11 | let ctl = match sysctl::Ctl::new("dev.cpu.0.temperature") { 12 | Ok(c) => c, 13 | Err(e) => { 14 | println!("Couldn't get dev.cpu.0.temperature: {}", e); 15 | return; 16 | } 17 | }; 18 | 19 | let name = ctl.name().expect("could not get sysctl name"); 20 | println!("Read sysctl {}", name); 21 | 22 | let d = ctl.description().expect("could not get description"); 23 | println!("Description: {:?}", d); 24 | 25 | let val_enum = ctl.value().expect("could not get value"); 26 | 27 | let temp = val_enum.as_temperature().unwrap(); 28 | println!( 29 | "Temperature: {:.2}K, {:.2}F, {:.2}C", 30 | temp.kelvin(), 31 | temp.fahrenheit(), 32 | temp.celsius() 33 | ); 34 | } 35 | 36 | #[cfg(not(target_os = "freebsd"))] 37 | fn main() { 38 | println!("This operation is only supported on FreeBSD."); 39 | } 40 | -------------------------------------------------------------------------------- /examples/value.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | extern crate sysctl; 5 | 6 | // Import the trait 7 | use sysctl::Sysctl; 8 | 9 | #[cfg(any(target_os = "macos", target_os = "freebsd"))] 10 | const CTLNAMES: &[&str] = &["kern.ostype"]; 11 | 12 | #[cfg(any(target_os = "linux", target_os = "android"))] 13 | const CTLNAMES: &[&str] = &["kernel.ostype", "kernel/ostype", "/proc/sys/kernel/ostype"]; 14 | 15 | fn print_ctl(ctlname: &str) -> Result<(), sysctl::SysctlError> { 16 | println!("Reading '{}'", ctlname); 17 | let ctl = sysctl::Ctl::new(ctlname)?; 18 | let desc = ctl.description()?; 19 | println!("Description: {}", desc); 20 | let val = ctl.value()?; 21 | println!("Value: {}", val); 22 | Ok(()) 23 | } 24 | 25 | fn main() { 26 | for ctlname in CTLNAMES { 27 | print_ctl(ctlname).unwrap_or_else(|e: sysctl::SysctlError| { 28 | eprintln!("Error: {:?}", e); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/value_as.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | extern crate libc; 5 | extern crate sysctl; 6 | 7 | // Import the trait 8 | use sysctl::Sysctl; 9 | 10 | // Converted from definition in from /usr/include/sys/time.h 11 | #[derive(Debug)] 12 | #[repr(C)] 13 | struct ClockInfo { 14 | hz: libc::c_int, /* clock frequency */ 15 | tick: libc::c_int, /* micro-seconds per hz tick */ 16 | spare: libc::c_int, 17 | stathz: libc::c_int, /* statistics clock frequency */ 18 | profhz: libc::c_int, /* profiling clock frequency */ 19 | } 20 | 21 | // Converted from definition in /usr/include/sys/resource.h 22 | #[repr(C)] 23 | struct LoadAvg { 24 | ldavg: [u32; 3], 25 | fscale: u64, 26 | } 27 | impl std::fmt::Debug for LoadAvg { 28 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 29 | let s = self.fscale as f32; 30 | write!( 31 | f, 32 | "LoadAvg {{ {:.2} {:.2} {:.2} }}", 33 | self.ldavg[0] as f32 / s, 34 | self.ldavg[1] as f32 / s, 35 | self.ldavg[2] as f32 / s 36 | ) 37 | } 38 | } 39 | 40 | #[cfg(any(target_os = "macos", target_os = "freebsd"))] 41 | fn main() { 42 | // Generic type to pass to function will be inferred if not specified on RHS 43 | println!("Read sysctl kern.clockrate as struct directly"); 44 | let val: Box = sysctl::Ctl::new("kern.clockrate") 45 | .expect("could not get sysctl: kern.clockrate") 46 | .value_as() 47 | .expect("could not read sysctl as struct"); 48 | println!("{:?}", val); 49 | println!(); 50 | 51 | // Pass type LoadAvg to generic function 52 | println!("Read sysctl vm.loadavg as struct directly"); 53 | let val = sysctl::Ctl::new("vm.loadavg") 54 | .expect("could not get sysctl: vm.loadavg") 55 | .value_as::() 56 | .expect("could not read sysctl as LoadAvg"); 57 | println!("{:?}", val); 58 | } 59 | 60 | #[cfg(any(target_os = "linux", target_os = "android"))] 61 | fn main() { 62 | println!("This operation is not supported on Linux."); 63 | } 64 | -------------------------------------------------------------------------------- /examples/value_oid_as.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | extern crate libc; 5 | extern crate sysctl; 6 | 7 | // Import the trait 8 | use sysctl::Sysctl; 9 | 10 | // Converted from definition in from /usr/include/sys/time.h 11 | #[derive(Debug)] 12 | #[repr(C)] 13 | struct ClockInfo { 14 | hz: libc::c_int, /* clock frequency */ 15 | tick: libc::c_int, /* micro-seconds per hz tick */ 16 | spare: libc::c_int, 17 | stathz: libc::c_int, /* statistics clock frequency */ 18 | profhz: libc::c_int, /* profiling clock frequency */ 19 | } 20 | 21 | #[cfg(any(target_os = "macos", target_os = "freebsd"))] 22 | fn main() { 23 | let oid: Vec = vec![libc::CTL_KERN, libc::KERN_CLOCKRATE]; 24 | let val: Box = sysctl::Ctl::Oid(oid).value_as().expect("could not get value"); 25 | println!("{:?}", val); 26 | } 27 | 28 | #[cfg(any(target_os = "linux", target_os = "android"))] 29 | fn main() { 30 | println!("This operation is not supported on Linux."); 31 | } 32 | -------------------------------------------------------------------------------- /examples/value_string.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | extern crate sysctl; 5 | 6 | // Import the trait 7 | use sysctl::Sysctl; 8 | 9 | #[cfg(any(target_os = "macos", target_os = "freebsd"))] 10 | const CTLNAMES: &[&str] = &["kern.osrevision"]; 11 | 12 | // On Linux all sysctl are String so it doesn't really make any sense to read an integer value here... 13 | #[cfg(any(target_os = "linux", target_os = "android"))] 14 | const CTLNAMES: &[&str] = &["kernel.overflowuid"]; 15 | 16 | fn print_ctl(ctlname: &str) -> Result<(), sysctl::SysctlError> { 17 | println!("Reading '{}'", ctlname); 18 | let ctl = sysctl::Ctl::new(ctlname)?; 19 | let description = ctl.description()?; 20 | println!("Description: {}", description); 21 | let val_string = ctl.value_string()?; 22 | println!("Value: {}", val_string); 23 | Ok(()) 24 | } 25 | 26 | fn main() { 27 | for ctlname in CTLNAMES { 28 | print_ctl(ctlname).unwrap_or_else(|e: sysctl::SysctlError| { 29 | eprintln!("Error: {:?}", e); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | // consts.rs 2 | 3 | // CTL* constants belong to libc crate but have not been added there yet. 4 | // They will be removed from here once in the libc crate. 5 | pub const CTL_MAXNAME: libc::c_uint = 24; 6 | 7 | pub const CTLTYPE: libc::c_uint = 0xf; /* mask for the type */ 8 | 9 | pub const CTLTYPE_NODE: libc::c_uint = 1; 10 | pub const CTLTYPE_INT: libc::c_uint = 2; 11 | pub const CTLTYPE_STRING: libc::c_uint = 3; 12 | pub const CTLTYPE_S64: libc::c_uint = 4; 13 | pub const CTLTYPE_OPAQUE: libc::c_uint = 5; 14 | pub const CTLTYPE_STRUCT: libc::c_uint = 5; 15 | pub const CTLTYPE_UINT: libc::c_uint = 6; 16 | pub const CTLTYPE_LONG: libc::c_uint = 7; 17 | pub const CTLTYPE_ULONG: libc::c_uint = 8; 18 | pub const CTLTYPE_U64: libc::c_uint = 9; 19 | pub const CTLTYPE_U8: libc::c_uint = 10; 20 | pub const CTLTYPE_U16: libc::c_uint = 11; 21 | pub const CTLTYPE_S8: libc::c_uint = 12; 22 | pub const CTLTYPE_S16: libc::c_uint = 13; 23 | pub const CTLTYPE_S32: libc::c_uint = 14; 24 | pub const CTLTYPE_U32: libc::c_uint = 15; 25 | 26 | pub const CTLFLAG_RD: libc::c_uint = 0x80000000; 27 | pub const CTLFLAG_WR: libc::c_uint = 0x40000000; 28 | pub const CTLFLAG_RW: libc::c_uint = 0x80000000 | 0x40000000; 29 | pub const CTLFLAG_DORMANT: libc::c_uint = 0x20000000; 30 | pub const CTLFLAG_ANYBODY: libc::c_uint = 0x10000000; 31 | pub const CTLFLAG_SECURE: libc::c_uint = 0x08000000; 32 | pub const CTLFLAG_PRISON: libc::c_uint = 0x04000000; 33 | pub const CTLFLAG_DYN: libc::c_uint = 0x02000000; 34 | pub const CTLFLAG_SKIP: libc::c_uint = 0x01000000; 35 | pub const CTLFLAG_TUN: libc::c_uint = 0x00080000; 36 | pub const CTLFLAG_RDTUN: libc::c_uint = CTLFLAG_RD | CTLFLAG_TUN; 37 | pub const CTLFLAG_RWTUN: libc::c_uint = CTLFLAG_RW | CTLFLAG_TUN; 38 | pub const CTLFLAG_MPSAFE: libc::c_uint = 0x00040000; 39 | pub const CTLFLAG_VNET: libc::c_uint = 0x00020000; 40 | pub const CTLFLAG_DYING: libc::c_uint = 0x00010000; 41 | pub const CTLFLAG_CAPRD: libc::c_uint = 0x00008000; 42 | pub const CTLFLAG_CAPWR: libc::c_uint = 0x00004000; 43 | pub const CTLFLAG_STATS: libc::c_uint = 0x00002000; 44 | pub const CTLFLAG_NOFETCH: libc::c_uint = 0x00001000; 45 | pub const CTLFLAG_CAPRW: libc::c_uint = CTLFLAG_CAPRD | CTLFLAG_CAPWR; 46 | pub const CTLFLAG_SECURE1: libc::c_uint = 134217728; 47 | pub const CTLFLAG_SECURE2: libc::c_uint = 135266304; 48 | pub const CTLFLAG_SECURE3: libc::c_uint = 136314880; 49 | 50 | pub const CTLMASK_SECURE: libc::c_uint = 15728640; 51 | pub const CTLSHIFT_SECURE: libc::c_uint = 20; 52 | -------------------------------------------------------------------------------- /src/ctl_error.rs: -------------------------------------------------------------------------------- 1 | // ctl_error.rs 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum SysctlError { 6 | #[error("no such sysctl: {0}")] 7 | NotFound(String), 8 | 9 | #[error("no matching type for value")] 10 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 11 | UnknownType, 12 | 13 | #[error("Error extracting value")] 14 | ExtractionError, 15 | 16 | #[error("Error parsing value")] 17 | ParseError, 18 | 19 | #[error("Support for type not implemented")] 20 | MissingImplementation, 21 | 22 | #[error("IO Error: {0}")] 23 | IoError(#[from] std::io::Error), 24 | 25 | #[error("Error parsing UTF-8 data: {0}")] 26 | Utf8Error(#[from] std::str::Utf8Error), 27 | 28 | #[error("Value is not readable")] 29 | NoReadAccess, 30 | 31 | #[error("Value is not writeable")] 32 | NoWriteAccess, 33 | 34 | #[error("Not supported by this platform")] 35 | NotSupported, 36 | 37 | #[error( 38 | "sysctl returned a short read: read {read} bytes, while a size of {reported} was reported" 39 | )] 40 | ShortRead { read: usize, reported: usize }, 41 | 42 | #[error("Error reading C String: String was not NUL-terminated.")] 43 | InvalidCStr(#[from] std::ffi::FromBytesWithNulError), 44 | 45 | #[error("Error Rust string contains nul bytes")] 46 | InvalidCString(#[from] std::ffi::NulError), 47 | } 48 | -------------------------------------------------------------------------------- /src/ctl_flags.rs: -------------------------------------------------------------------------------- 1 | // ctl_flags.rs 2 | 3 | use super::consts::*; 4 | 5 | // Represents control flags of a sysctl 6 | bitflags! { 7 | pub struct CtlFlags : libc::c_uint { 8 | /// Allow reads of variable 9 | const RD = CTLFLAG_RD; 10 | 11 | /// Allow writes to the variable 12 | const WR = CTLFLAG_WR; 13 | 14 | const RW = Self::RD.bits() | Self::WR.bits(); 15 | 16 | /// This sysctl is not active yet 17 | const DORMANT = CTLFLAG_DORMANT; 18 | 19 | /// All users can set this var 20 | const ANYBODY = CTLFLAG_ANYBODY; 21 | 22 | /// Permit set only if securelevel<=0 23 | const SECURE = CTLFLAG_SECURE; 24 | 25 | /// Prisoned roots can fiddle 26 | const PRISON = CTLFLAG_PRISON; 27 | 28 | /// Dynamic oid - can be freed 29 | const DYN = CTLFLAG_DYN; 30 | 31 | /// Skip this sysctl when listing 32 | const SKIP = CTLFLAG_DORMANT; 33 | 34 | /// Secure level 35 | const SECURE_MASK = 0x00F00000; 36 | 37 | /// Default value is loaded from getenv() 38 | const TUN = CTLFLAG_TUN; 39 | 40 | /// Readable tunable 41 | const RDTUN = Self::RD.bits() | Self::TUN.bits(); 42 | 43 | /// Readable and writeable tunable 44 | const RWTUN = Self::RW.bits() | Self::TUN.bits(); 45 | 46 | /// Handler is MP safe 47 | const MPSAFE = CTLFLAG_MPSAFE; 48 | 49 | /// Prisons with vnet can fiddle 50 | const VNET = CTLFLAG_VNET; 51 | 52 | /// Oid is being removed 53 | const DYING = CTLFLAG_DYING; 54 | 55 | /// Can be read in capability mode 56 | const CAPRD = CTLFLAG_CAPRD; 57 | 58 | /// Can be written in capability mode 59 | const CAPWR = CTLFLAG_CAPWR; 60 | 61 | /// Statistics; not a tuneable 62 | const STATS = CTLFLAG_STATS; 63 | 64 | /// Don't fetch tunable from getenv() 65 | const NOFETCH = CTLFLAG_NOFETCH; 66 | 67 | /// Can be read and written in capability mode 68 | const CAPRW = Self::CAPRD.bits() | Self::CAPWR.bits(); 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use crate::Sysctl; 75 | 76 | #[test] 77 | fn ctl_flags() { 78 | // This sysctl should be read-only. 79 | #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 80 | let ctl: crate::Ctl = crate::Ctl::new("kern.ostype").unwrap(); 81 | #[cfg(any(target_os = "android", target_os = "linux"))] 82 | let ctl: crate::Ctl = crate::Ctl::new("kernel.ostype").unwrap(); 83 | 84 | let flags: crate::CtlFlags = ctl.flags().unwrap(); 85 | 86 | assert_eq!(flags.bits() & crate::CTLFLAG_RD, crate::CTLFLAG_RD); 87 | assert_eq!(flags.bits() & crate::CTLFLAG_WR, 0); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/ctl_info.rs: -------------------------------------------------------------------------------- 1 | // ctl_info.rs 2 | 3 | use ctl_flags::*; 4 | use ctl_type::*; 5 | 6 | #[derive(Debug, PartialEq)] 7 | /// A structure representing control metadata 8 | pub struct CtlInfo { 9 | /// The control type. 10 | pub ctl_type: CtlType, 11 | 12 | /// A string which specifies the format of the OID in 13 | /// a symbolic way. 14 | /// 15 | /// This format is used as a hint by sysctl(8) to 16 | /// apply proper data formatting for display purposes. 17 | /// 18 | /// Formats defined in sysctl(9): 19 | /// * `N` node 20 | /// * `A` char * 21 | /// * `I` int 22 | /// * `IK[n]` temperature in Kelvin, multiplied by an optional single 23 | /// digit power of ten scaling factor: 1 (default) gives deciKelvin, 24 | /// 0 gives Kelvin, 3 gives milliKelvin 25 | /// * `IU` unsigned int 26 | /// * `L` long 27 | /// * `LU` unsigned long 28 | /// * `Q` quad_t 29 | /// * `QU` u_quad_t 30 | /// * `S,TYPE` struct TYPE structures 31 | pub fmt: String, 32 | 33 | pub flags: u32, 34 | } 35 | 36 | #[cfg(target_os = "freebsd")] 37 | impl CtlInfo { 38 | /// Is this sysctl a temperature? 39 | pub fn is_temperature(&self) -> bool { 40 | self.fmt.starts_with("IK") 41 | } 42 | } 43 | 44 | impl CtlInfo { 45 | /// Return the flags for this sysctl. 46 | pub fn flags(&self) -> CtlFlags { 47 | CtlFlags::from_bits_truncate(self.flags) 48 | } 49 | 50 | /// If the sysctl is a structure, return the structure type string. 51 | /// 52 | /// Checks whether the format string starts with `S,` and returns the rest 53 | /// of the format string or None if the format String does not have a struct 54 | /// hint. 55 | pub fn struct_type(&self) -> Option { 56 | if !self.fmt.starts_with("S,") { 57 | return None; 58 | } 59 | 60 | Some(self.fmt[2..].into()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ctl_type.rs: -------------------------------------------------------------------------------- 1 | // ctl_type.rs 2 | 3 | use ctl_value::*; 4 | 5 | /// An Enum that represents a sysctl's type information. 6 | /// 7 | /// # Example 8 | /// 9 | /// ``` 10 | /// # use sysctl::Sysctl; 11 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.osrevision") { 12 | /// if let Ok(value) = ctl.value() { 13 | /// let val_type: sysctl::CtlType = value.into(); 14 | /// assert_eq!(val_type, sysctl::CtlType::Int); 15 | /// } 16 | /// } 17 | /// ``` 18 | #[derive(Debug, Copy, Clone, PartialEq)] 19 | #[repr(u32)] 20 | pub enum CtlType { 21 | Node = 1, 22 | Int = 2, 23 | String = 3, 24 | S64 = 4, 25 | Struct = 5, 26 | Uint = 6, 27 | Long = 7, 28 | Ulong = 8, 29 | U64 = 9, 30 | U8 = 10, 31 | U16 = 11, 32 | S8 = 12, 33 | S16 = 13, 34 | S32 = 14, 35 | U32 = 15, 36 | // Added custom types below 37 | None = 0, 38 | #[cfg(target_os = "freebsd")] 39 | Temperature = 16, 40 | } 41 | impl std::convert::From for CtlType { 42 | fn from(t: u32) -> Self { 43 | assert!(t <= 16); 44 | unsafe { std::mem::transmute(t) } 45 | } 46 | } 47 | impl std::convert::From<&CtlValue> for CtlType { 48 | fn from(t: &CtlValue) -> Self { 49 | match t { 50 | CtlValue::None => CtlType::None, 51 | CtlValue::Node(_) => CtlType::Node, 52 | CtlValue::Int(_) => CtlType::Int, 53 | CtlValue::String(_) => CtlType::String, 54 | CtlValue::S64(_) => CtlType::S64, 55 | CtlValue::Struct(_) => CtlType::Struct, 56 | CtlValue::Uint(_) => CtlType::Uint, 57 | CtlValue::Long(_) => CtlType::Long, 58 | CtlValue::Ulong(_) => CtlType::Ulong, 59 | CtlValue::U64(_) => CtlType::U64, 60 | CtlValue::U8(_) => CtlType::U8, 61 | CtlValue::U16(_) => CtlType::U16, 62 | CtlValue::S8(_) => CtlType::S8, 63 | CtlValue::S16(_) => CtlType::S16, 64 | CtlValue::S32(_) => CtlType::S32, 65 | CtlValue::U32(_) => CtlType::U32, 66 | #[cfg(target_os = "freebsd")] 67 | CtlValue::Temperature(_) => CtlType::Temperature, 68 | } 69 | } 70 | } 71 | impl std::convert::From for CtlType { 72 | fn from(t: CtlValue) -> Self { 73 | Self::from(&t) 74 | } 75 | } 76 | 77 | impl CtlType { 78 | pub fn min_type_size(&self) -> usize { 79 | match self { 80 | CtlType::None => 0, 81 | CtlType::Node => 0, 82 | CtlType::Int => std::mem::size_of::(), 83 | CtlType::String => 0, 84 | CtlType::S64 => std::mem::size_of::(), 85 | CtlType::Struct => 0, 86 | CtlType::Uint => std::mem::size_of::(), 87 | CtlType::Long => std::mem::size_of::(), 88 | CtlType::Ulong => std::mem::size_of::(), 89 | CtlType::U64 => std::mem::size_of::(), 90 | CtlType::U8 => std::mem::size_of::(), 91 | CtlType::U16 => std::mem::size_of::(), 92 | CtlType::S8 => std::mem::size_of::(), 93 | CtlType::S16 => std::mem::size_of::(), 94 | CtlType::S32 => std::mem::size_of::(), 95 | CtlType::U32 => std::mem::size_of::(), 96 | // Added custom types below 97 | #[cfg(target_os = "freebsd")] 98 | CtlType::Temperature => 0, 99 | } 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/ctl_value.rs: -------------------------------------------------------------------------------- 1 | // ctl_value.rs 2 | 3 | #[cfg(target_os = "freebsd")] 4 | use temperature::Temperature; 5 | use enum_as_inner::EnumAsInner; 6 | 7 | /// An Enum that holds all values returned by sysctl calls. 8 | /// Extract inner value with accessors like `as_int()`. 9 | /// 10 | /// # Example 11 | /// 12 | /// ``` 13 | /// # use sysctl::Sysctl; 14 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.osrevision") { 15 | /// if let Ok(r) = ctl.value() { 16 | /// if let Some(val) = r.as_int() { 17 | /// println!("Value: {}", val); 18 | /// } 19 | /// } 20 | /// } 21 | /// ``` 22 | #[derive(Debug, EnumAsInner, PartialEq, PartialOrd)] 23 | pub enum CtlValue { 24 | None, 25 | Node(Vec), 26 | Int(i32), 27 | String(String), 28 | S64(i64), 29 | Struct(Vec), 30 | Uint(u32), 31 | Long(i64), 32 | Ulong(u64), 33 | U64(u64), 34 | U8(u8), 35 | U16(u16), 36 | S8(i8), 37 | S16(i16), 38 | S32(i32), 39 | U32(u32), 40 | #[cfg(target_os = "freebsd")] 41 | Temperature(Temperature), 42 | } 43 | 44 | impl std::fmt::Display for CtlValue { 45 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 46 | let s = match self { 47 | CtlValue::None => "[None]".to_owned(), 48 | CtlValue::Int(i) => format!("{}", i), 49 | CtlValue::Uint(i) => format!("{}", i), 50 | CtlValue::Long(i) => format!("{}", i), 51 | CtlValue::Ulong(i) => format!("{}", i), 52 | CtlValue::U8(i) => format!("{}", i), 53 | CtlValue::U16(i) => format!("{}", i), 54 | CtlValue::U32(i) => format!("{}", i), 55 | CtlValue::U64(i) => format!("{}", i), 56 | CtlValue::S8(i) => format!("{}", i), 57 | CtlValue::S16(i) => format!("{}", i), 58 | CtlValue::S32(i) => format!("{}", i), 59 | CtlValue::S64(i) => format!("{}", i), 60 | CtlValue::Struct(_) => "[Opaque Struct]".to_owned(), 61 | CtlValue::Node(_) => "[Node]".to_owned(), 62 | CtlValue::String(s) => s.to_owned(), 63 | #[cfg(target_os = "freebsd")] 64 | CtlValue::Temperature(t) => format!("{}", t.kelvin()), 65 | }; 66 | write!(f, "{}", s) 67 | } 68 | } 69 | 70 | #[cfg(all(test, any(target_os = "linux", target_os = "android")))] 71 | mod tests_linux { 72 | use crate::sys; 73 | use crate::Sysctl; 74 | 75 | #[test] 76 | fn ctl_value_string() { 77 | // NOTE: Some linux distributions require Root permissions 78 | // e.g Debian. 79 | let output = std::process::Command::new("sysctl") 80 | .arg("-n") 81 | .arg("kernel.version") 82 | .output() 83 | .expect("failed to execute process"); 84 | let ver = String::from_utf8_lossy(&output.stdout); 85 | let s = match sys::funcs::value("/proc/sys/kernel/version") { 86 | Ok(crate::CtlValue::String(s)) => s, 87 | _ => panic!("crate::value() returned Error"), 88 | }; 89 | assert_eq!(s.trim(), ver.trim()); 90 | 91 | let kernversion = crate::Ctl::new("kernel.version").unwrap(); 92 | let s = match kernversion.value() { 93 | Ok(crate::CtlValue::String(s)) => s, 94 | _ => "...".into(), 95 | }; 96 | assert_eq!(s.trim(), ver.trim()); 97 | } 98 | } 99 | 100 | #[cfg(all(test, any(target_os = "freebsd", target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 101 | mod tests_unix { 102 | use crate::sys; 103 | use crate::Sysctl; 104 | 105 | #[test] 106 | fn ctl_value_string() { 107 | let output = std::process::Command::new("sysctl") 108 | .arg("-n") 109 | .arg("kern.version") 110 | .output() 111 | .expect("failed to execute process"); 112 | let ver = String::from_utf8_lossy(&output.stdout); 113 | let ctl = crate::Ctl::new("kern.version").expect("Ctl::new"); 114 | let s = match ctl.value() { 115 | Ok(crate::CtlValue::String(s)) => s, 116 | _ => "...".into(), 117 | }; 118 | assert_eq!(s.trim(), ver.trim()); 119 | 120 | let kernversion = crate::Ctl::new("kern.version").unwrap(); 121 | let s = match kernversion.value() { 122 | Ok(crate::CtlValue::String(s)) => s, 123 | _ => "...".into(), 124 | }; 125 | assert_eq!(s.trim(), ver.trim()); 126 | } 127 | 128 | #[test] 129 | fn ctl_value_int() { 130 | let output = std::process::Command::new("sysctl") 131 | .arg("-n") 132 | .arg("kern.osrevision") 133 | .output() 134 | .expect("failed to execute process"); 135 | let rev_str = String::from_utf8_lossy(&output.stdout); 136 | let rev = rev_str.trim().parse::().unwrap(); 137 | 138 | let ctl = 139 | crate::Ctl::new("kern.osrevision").expect("Could not get kern.osrevision sysctl."); 140 | let n = match ctl.value() { 141 | Ok(crate::CtlValue::Int(n)) => n, 142 | Ok(_) => 0, 143 | Err(_) => 0, 144 | }; 145 | assert_eq!(n, rev); 146 | } 147 | 148 | #[test] 149 | fn ctl_value_oid_int() { 150 | let output = std::process::Command::new("sysctl") 151 | .arg("-n") 152 | .arg("kern.osrevision") 153 | .output() 154 | .expect("failed to execute process"); 155 | let rev_str = String::from_utf8_lossy(&output.stdout); 156 | let rev = rev_str.trim().parse::().unwrap(); 157 | let n = match sys::funcs::value_oid(&mut vec![libc::CTL_KERN, libc::KERN_OSREV]) { 158 | Ok(crate::CtlValue::Int(n)) => n, 159 | Ok(_) => 0, 160 | Err(_) => 0, 161 | }; 162 | assert_eq!(n, rev); 163 | } 164 | 165 | #[test] 166 | fn ctl_struct_type() { 167 | let info = crate::CtlInfo { 168 | ctl_type: crate::CtlType::Int, 169 | fmt: "S,TYPE".into(), 170 | flags: 0, 171 | }; 172 | 173 | assert_eq!(info.struct_type(), Some("TYPE".into())); 174 | 175 | let info = crate::CtlInfo { 176 | ctl_type: crate::CtlType::Int, 177 | fmt: "I".into(), 178 | flags: 0, 179 | }; 180 | assert_eq!(info.struct_type(), None); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // lib.rs 2 | 3 | //! A simplified interface to the `sysctl` system call. 4 | //! 5 | //! # Example: Get value 6 | //! ``` 7 | //! # use sysctl::Sysctl; 8 | //! #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos", target_os = "freebsd"))] 9 | //! const CTLNAME: &str = "kern.ostype"; 10 | //! #[cfg(any(target_os = "linux", target_os = "android"))] 11 | //! const CTLNAME: &str = "kernel.ostype"; 12 | //! 13 | //! let ctl = sysctl::Ctl::new(CTLNAME).unwrap(); 14 | //! let desc = ctl.description().unwrap(); 15 | //! println!("Description: {}", desc); 16 | //! let val = ctl.value().unwrap(); 17 | //! println!("Value: {}", val); 18 | //! // On Linux all sysctls are String type. Use the following for 19 | //! // cross-platform compatibility: 20 | //! let str_val = ctl.value_string().unwrap(); 21 | //! println!("String value: {}", str_val); 22 | //! ``` 23 | //! 24 | //! # Example: Get value as struct 25 | //! ``` 26 | //! // Not available on Linux 27 | //! # use sysctl::Sysctl; 28 | //! #[derive(Debug, Default)] 29 | //! #[repr(C)] 30 | //! struct ClockInfo { 31 | //! hz: libc::c_int, /* clock frequency */ 32 | //! tick: libc::c_int, /* micro-seconds per hz tick */ 33 | //! spare: libc::c_int, 34 | //! stathz: libc::c_int, /* statistics clock frequency */ 35 | //! profhz: libc::c_int, /* profiling clock frequency */ 36 | //! } 37 | //! # #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos", target_os = "freebsd"))] 38 | //! let val: Box = sysctl::Ctl::new("kern.clockrate").unwrap().value_as().unwrap(); 39 | //! # #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos", target_os = "freebsd"))] 40 | //! println!("{:?}", val); 41 | //! ``` 42 | 43 | #[macro_use] 44 | extern crate bitflags; 45 | extern crate byteorder; 46 | extern crate enum_as_inner; 47 | extern crate libc; 48 | extern crate thiserror; 49 | 50 | #[cfg(any(target_os = "android", target_os = "linux"))] 51 | extern crate walkdir; 52 | 53 | #[cfg(any(target_os = "android", target_os = "linux"))] 54 | #[path = "linux/mod.rs"] 55 | mod sys; 56 | 57 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos", target_os = "freebsd"))] 58 | #[path = "unix/mod.rs"] 59 | mod sys; 60 | 61 | mod consts; 62 | mod ctl_error; 63 | mod ctl_flags; 64 | mod ctl_info; 65 | mod ctl_type; 66 | mod ctl_value; 67 | #[cfg(target_os = "freebsd")] 68 | mod temperature; 69 | mod traits; 70 | 71 | pub use consts::*; 72 | pub use ctl_error::*; 73 | pub use ctl_flags::*; 74 | pub use ctl_info::*; 75 | pub use ctl_type::*; 76 | pub use ctl_value::*; 77 | pub use sys::ctl::*; 78 | pub use sys::ctl_iter::*; 79 | #[cfg(target_os = "freebsd")] 80 | pub use temperature::Temperature; 81 | pub use traits::Sysctl; 82 | -------------------------------------------------------------------------------- /src/linux/ctl.rs: -------------------------------------------------------------------------------- 1 | // linux/ctl.rs 2 | 3 | use super::funcs::{path_to_name, set_value, value}; 4 | use consts::*; 5 | use ctl_error::SysctlError; 6 | use ctl_flags::CtlFlags; 7 | use ctl_info::CtlInfo; 8 | use ctl_type::CtlType; 9 | use ctl_value::CtlValue; 10 | use std::str::FromStr; 11 | use traits::Sysctl; 12 | 13 | /// This struct represents a system control. 14 | #[derive(Debug, Clone, PartialEq)] 15 | pub struct Ctl { 16 | name: String, 17 | } 18 | 19 | impl FromStr for Ctl { 20 | type Err = SysctlError; 21 | 22 | fn from_str(name: &str) -> Result { 23 | let ctl = Ctl { 24 | name: path_to_name(name), 25 | }; 26 | let _ = 27 | std::fs::File::open(ctl.path()).map_err(|_| SysctlError::NotFound(name.to_owned()))?; 28 | Ok(ctl) 29 | } 30 | } 31 | 32 | impl Ctl { 33 | pub fn path(&self) -> String { 34 | format!("/proc/sys/{}", self.name.replace(".", "/")) 35 | } 36 | } 37 | 38 | impl Sysctl for Ctl { 39 | fn new(name: &str) -> Result { 40 | Ctl::from_str(name) 41 | } 42 | 43 | fn new_with_type(name: &str, _ctl_type: CtlType, _fmt: &str) -> Result { 44 | Ctl::from_str(name) 45 | } 46 | 47 | fn name(&self) -> Result { 48 | Ok(self.name.clone()) 49 | } 50 | 51 | fn value_type(&self) -> Result { 52 | let md = std::fs::metadata(&self.path()).map_err(SysctlError::IoError)?; 53 | if md.is_dir() { 54 | Ok(CtlType::Node) 55 | } else { 56 | Ok(CtlType::String) 57 | } 58 | } 59 | 60 | fn description(&self) -> Result { 61 | Ok("[N/A]".to_owned()) 62 | } 63 | 64 | fn value(&self) -> Result { 65 | value(&self.path()) 66 | } 67 | 68 | fn value_string(&self) -> Result { 69 | self.value().map(|v| format!("{}", v)) 70 | } 71 | 72 | fn value_as(&self) -> Result, SysctlError> { 73 | Err(SysctlError::NotSupported) 74 | } 75 | 76 | fn set_value(&self, value: CtlValue) -> Result { 77 | set_value(&self.path(), value) 78 | } 79 | 80 | fn set_value_string(&self, value: &str) -> Result { 81 | self.set_value(CtlValue::String(value.to_owned()))?; 82 | self.value_string() 83 | } 84 | 85 | fn flags(&self) -> Result { 86 | Ok(self.info()?.flags()) 87 | } 88 | 89 | fn info(&self) -> Result { 90 | let md = std::fs::metadata(&self.path()).map_err(SysctlError::IoError)?; 91 | let mut flags = 0; 92 | if md.permissions().readonly() { 93 | flags |= CTLFLAG_RD; 94 | } else { 95 | flags |= CTLFLAG_RW; 96 | } 97 | let s = CtlInfo { 98 | ctl_type: CtlType::String, 99 | fmt: "".to_owned(), 100 | flags: flags, 101 | }; 102 | Ok(s) 103 | } 104 | } 105 | 106 | #[cfg(test)] 107 | mod tests { 108 | use crate::Sysctl; 109 | 110 | #[test] 111 | fn ctl_new() { 112 | let _ = super::Ctl::new("kernel.ostype").expect("Ctl::new"); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/linux/ctl_iter.rs: -------------------------------------------------------------------------------- 1 | // linux/ctl_iter.rs 2 | 3 | use super::ctl::Ctl; 4 | use ctl_error::SysctlError; 5 | use traits::Sysctl; 6 | 7 | /// An iterator over Sysctl entries. 8 | pub struct CtlIter { 9 | direntries: Vec, 10 | base: String, 11 | cur_idx: usize, 12 | } 13 | 14 | impl CtlIter { 15 | /// Return an iterator over the complete sysctl tree. 16 | pub fn root() -> Self { 17 | let entries: Vec = walkdir::WalkDir::new("/proc/sys") 18 | .sort_by(|a, b| a.path().cmp(b.path())) 19 | .follow_links(false) 20 | .into_iter() 21 | .filter_map(|e| e.ok()) 22 | .filter(|e| e.file_type().is_file()) 23 | .collect(); 24 | CtlIter { 25 | direntries: entries, 26 | base: "/proc/sys".to_owned(), 27 | cur_idx: 0, 28 | } 29 | } 30 | 31 | /// Return an iterator over all sysctl entries below the given node. 32 | pub fn below(node: Ctl) -> Self { 33 | let root = node.path(); 34 | let entries: Vec = walkdir::WalkDir::new(&root) 35 | .sort_by(|a, b| a.path().cmp(b.path())) 36 | .follow_links(false) 37 | .into_iter() 38 | .filter_map(|e| e.ok()) 39 | .filter(|e| e.file_type().is_file()) 40 | .collect(); 41 | CtlIter { 42 | direntries: entries, 43 | base: root, 44 | cur_idx: 0, 45 | } 46 | } 47 | } 48 | 49 | impl Iterator for CtlIter { 50 | type Item = Result; 51 | 52 | fn next(&mut self) -> Option { 53 | if self.cur_idx >= self.direntries.len() { 54 | return None; 55 | } 56 | 57 | let e: &walkdir::DirEntry = &self.direntries[self.cur_idx]; 58 | self.cur_idx += 1; 59 | 60 | // We continue iterating as long as the oid starts with the base 61 | if let Some(path) = e.path().to_str() { 62 | if path.starts_with(&self.base) { 63 | Some(Ctl::new(path)) 64 | } else { 65 | None 66 | } 67 | } else { 68 | Some(Err(SysctlError::ParseError)) 69 | } 70 | } 71 | } 72 | 73 | /// Ctl implements the IntoIterator trait to allow for easy iteration 74 | /// over nodes. 75 | /// 76 | /// # Example 77 | /// 78 | /// ``` 79 | /// # use sysctl::Sysctl; 80 | /// # 81 | /// let kern = sysctl::Ctl::new("kernel"); 82 | /// for ctl in kern { 83 | /// println!("{}", ctl.name().unwrap()); 84 | /// } 85 | /// ``` 86 | impl IntoIterator for Ctl { 87 | type Item = Result; 88 | type IntoIter = CtlIter; 89 | 90 | fn into_iter(self: Self) -> Self::IntoIter { 91 | CtlIter::below(self) 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use crate::Sysctl; 98 | 99 | #[test] 100 | fn ctl_iter_iterate_all() { 101 | let root = crate::CtlIter::root(); 102 | let all_ctls: Vec = root.into_iter().filter_map(Result::ok).collect(); 103 | assert_ne!(all_ctls.len(), 0); 104 | for ctl in &all_ctls { 105 | println!("{:?}", ctl.name()); 106 | } 107 | } 108 | 109 | #[test] 110 | fn ctl_iter_below_compare_outputs() { 111 | // NOTE: Some linux distributions require Root permissions 112 | // e.g Debian. 113 | let output = std::process::Command::new("sysctl") 114 | .arg("user") 115 | .output() 116 | .expect("failed to execute process"); 117 | let expected = String::from_utf8_lossy(&output.stdout); 118 | 119 | let node = crate::Ctl::new("user").expect("could not get node"); 120 | let ctls = crate::CtlIter::below(node); 121 | let mut actual: Vec = vec![]; 122 | 123 | for ctl in ctls { 124 | let ctl = match ctl { 125 | Err(_) => panic!("ctl error"), 126 | Ok(s) => s, 127 | }; 128 | 129 | let name = match ctl.name() { 130 | Ok(s) => s, 131 | Err(_) => panic!("get ctl name"), 132 | }; 133 | 134 | match ctl.value_type().expect("could not get value type") { 135 | crate::CtlType::String => { 136 | actual.push(format!( 137 | "{} = {}", 138 | name, 139 | ctl.value_string() 140 | .expect(&format!("could not get value as string for {}", name)) 141 | )); 142 | } 143 | _ => panic!("sysctl not string type"), 144 | }; 145 | } 146 | assert_eq!(actual.join("\n").trim(), expected.trim()); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/linux/funcs.rs: -------------------------------------------------------------------------------- 1 | // linux/funcs.rs 2 | 3 | use ctl_error::*; 4 | use ctl_value::*; 5 | 6 | use std::io::{Read, Write}; 7 | 8 | pub fn path_to_name(name: &str) -> String { 9 | name.to_owned() 10 | .replace("/proc/sys/", "") 11 | .replace("..", ".") 12 | .replace("/", ".") 13 | } 14 | 15 | pub fn value(name: &str) -> Result { 16 | let file_res = std::fs::OpenOptions::new() 17 | .read(true) 18 | .write(false) 19 | .open(&name); 20 | 21 | file_res 22 | .map(|mut file| { 23 | let mut v = String::new(); 24 | file.read_to_string(&mut v)?; 25 | Ok(CtlValue::String(v.trim().to_owned())) 26 | }) 27 | .map_err(|e| { 28 | if e.kind() == std::io::ErrorKind::NotFound { 29 | SysctlError::NotFound(name.into()) 30 | } else { 31 | e.into() 32 | } 33 | })? 34 | } 35 | 36 | pub fn set_value(name: &str, v: CtlValue) -> Result { 37 | let file_res = std::fs::OpenOptions::new() 38 | .read(false) 39 | .write(true) 40 | .open(&name); 41 | 42 | file_res 43 | .map(|mut file| match v { 44 | CtlValue::String(v) => { 45 | file.write_all(&v.as_bytes())?; 46 | value(&name) 47 | } 48 | _ => Err(std::io::Error::from(std::io::ErrorKind::InvalidData).into()), 49 | }) 50 | .map_err(|e| { 51 | if e.kind() == std::io::ErrorKind::NotFound { 52 | SysctlError::NotFound(name.into()) 53 | } else { 54 | e.into() 55 | } 56 | })? 57 | } 58 | -------------------------------------------------------------------------------- /src/linux/mod.rs: -------------------------------------------------------------------------------- 1 | // linux/mod.rs 2 | 3 | pub mod ctl; 4 | pub mod ctl_iter; 5 | pub mod funcs; 6 | -------------------------------------------------------------------------------- /src/temperature.rs: -------------------------------------------------------------------------------- 1 | // temperature.rs 2 | 3 | use byteorder::ByteOrder; 4 | use ctl_error::SysctlError; 5 | use ctl_info::CtlInfo; 6 | use ctl_type::CtlType; 7 | use ctl_value::CtlValue; 8 | 9 | use std::f32; 10 | 11 | /// A custom type for temperature sysctls. 12 | /// 13 | /// # Example 14 | /// ``` 15 | /// # use sysctl::Sysctl; 16 | /// if let Ok(ctl) = sysctl::Ctl::new("dev.cpu.0.temperature") { 17 | /// let val = ctl.value().unwrap(); 18 | /// let temp = val.as_temperature().unwrap(); 19 | /// println!("Temperature: {:.2}K, {:.2}F, {:.2}C", 20 | /// temp.kelvin(), 21 | /// temp.fahrenheit(), 22 | /// temp.celsius()); 23 | /// } 24 | /// ``` 25 | /// Not available on MacOS 26 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] 27 | pub struct Temperature { 28 | value: f32, // Kelvin 29 | } 30 | impl Temperature { 31 | pub fn kelvin(&self) -> f32 { 32 | self.value 33 | } 34 | pub fn celsius(&self) -> f32 { 35 | self.value - 273.15 36 | } 37 | pub fn fahrenheit(&self) -> f32 { 38 | 1.8 * self.celsius() + 32.0 39 | } 40 | } 41 | 42 | pub fn temperature(info: &CtlInfo, val: &[u8]) -> Result { 43 | let prec: u32 = { 44 | match info.fmt.len() { 45 | l if l > 2 => match info.fmt[2..3].parse::() { 46 | Ok(x) if x <= 9 => x, 47 | _ => 1, 48 | }, 49 | _ => 1, 50 | } 51 | }; 52 | 53 | let base = 10u32.pow(prec) as f32; 54 | 55 | let make_temp = move |f: f32| -> Result { 56 | Ok(CtlValue::Temperature(Temperature { value: f / base })) 57 | }; 58 | 59 | match info.ctl_type { 60 | CtlType::Int => make_temp(byteorder::LittleEndian::read_i32(&val) as f32), 61 | CtlType::S64 => make_temp(byteorder::LittleEndian::read_i64(&val) as f32), 62 | CtlType::Uint => make_temp(byteorder::LittleEndian::read_u32(&val) as f32), 63 | CtlType::Long => make_temp(byteorder::LittleEndian::read_i64(&val) as f32), 64 | CtlType::Ulong => make_temp(byteorder::LittleEndian::read_u64(&val) as f32), 65 | CtlType::U64 => make_temp(byteorder::LittleEndian::read_u64(&val) as f32), 66 | CtlType::U8 => make_temp(val[0] as u8 as f32), 67 | CtlType::U16 => make_temp(byteorder::LittleEndian::read_u16(&val) as f32), 68 | CtlType::S8 => make_temp(val[0] as i8 as f32), 69 | CtlType::S16 => make_temp(byteorder::LittleEndian::read_i16(&val) as f32), 70 | CtlType::S32 => make_temp(byteorder::LittleEndian::read_i32(&val) as f32), 71 | CtlType::U32 => make_temp(byteorder::LittleEndian::read_u32(&val) as f32), 72 | _ => Err(SysctlError::UnknownType), 73 | } 74 | } 75 | 76 | #[cfg(all(test, target_os = "freebsd"))] 77 | mod tests_freebsd { 78 | use byteorder::WriteBytesExt; 79 | 80 | #[test] 81 | fn ctl_temperature_ik() { 82 | let info = crate::CtlInfo { 83 | ctl_type: crate::CtlType::Int, 84 | fmt: "IK".into(), 85 | flags: 0, 86 | }; 87 | let mut val = vec![]; 88 | // Default value (IK) in deciKelvin integer 89 | val.write_i32::(3330) 90 | .expect("Error parsing value to byte array"); 91 | 92 | let t = super::temperature(&info, &val).unwrap(); 93 | let tt = t.as_temperature().unwrap(); 94 | assert!(tt.kelvin() - 333.0 < 0.1); 95 | assert!(tt.celsius() - 59.85 < 0.1); 96 | assert!(tt.fahrenheit() - 139.73 < 0.1); 97 | } 98 | 99 | #[test] 100 | fn ctl_temperature_ik3() { 101 | let info = crate::CtlInfo { 102 | ctl_type: crate::CtlType::Int, 103 | fmt: "IK3".into(), 104 | flags: 0, 105 | }; 106 | let mut val = vec![]; 107 | // Set value in milliKelvin 108 | val.write_i32::(333000) 109 | .expect("Error parsing value to byte array"); 110 | 111 | let t = super::temperature(&info, &val).unwrap(); 112 | let tt = t.as_temperature().unwrap(); 113 | assert!(tt.kelvin() - 333.0 < 0.1); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | // traits.rs 2 | 3 | use ctl_error::SysctlError; 4 | use ctl_flags::CtlFlags; 5 | use ctl_info::CtlInfo; 6 | use ctl_type::CtlType; 7 | use ctl_value::CtlValue; 8 | 9 | pub trait Sysctl { 10 | /// Construct a Ctl from the name. 11 | /// 12 | /// Returns a result containing the struct Ctl on success or a SysctlError 13 | /// on failure. 14 | /// 15 | /// # Example 16 | /// ``` 17 | /// # use sysctl::Sysctl; 18 | /// # 19 | /// let ctl = sysctl::Ctl::new("kern.ostype"); 20 | /// ``` 21 | /// 22 | /// If the sysctl does not exist, `Err(SysctlError::NotFound)` is returned. 23 | /// ``` 24 | /// # use sysctl::Sysctl; 25 | /// # 26 | /// let ctl = sysctl::Ctl::new("this.sysctl.does.not.exist"); 27 | /// match ctl { 28 | /// Err(sysctl::SysctlError::NotFound(_)) => (), 29 | /// Err(e) => panic!(format!("Wrong error type returned: {:?}", e)), 30 | /// Ok(_) => panic!("Nonexistent sysctl seems to exist"), 31 | /// } 32 | /// ``` 33 | fn new(name: &str) -> Result 34 | where 35 | Self: std::marker::Sized; 36 | 37 | /// Construct a Ctl from the name, type and format. 38 | /// 39 | /// Returns a result containing the struct Ctl on success or a SysctlError 40 | /// on failure. 41 | /// 42 | /// # Example 43 | /// ``` 44 | /// # use sysctl::{CtlType, Sysctl}; 45 | /// # 46 | /// let ctl = sysctl::Ctl::new_with_type("kern.ostype", CtlType::String, ""); 47 | /// ``` 48 | /// 49 | /// If the sysctl does not exist, `Err(SysctlError::NotFound)` is returned. 50 | /// ``` 51 | /// # use sysctl::{CtlType, Sysctl}; 52 | /// # 53 | /// let ctl = sysctl::Ctl::new_with_type("this.sysctl.does.not.exist", CtlType::String, ""); 54 | /// match ctl { 55 | /// Err(sysctl::SysctlError::NotFound(_)) => (), 56 | /// Err(e) => panic!("Wrong error type returned: {:?}", e), 57 | /// Ok(_) => panic!("Nonexistent sysctl seems to exist"), 58 | /// } 59 | /// ``` 60 | fn new_with_type(name: &str, ctl_type: CtlType, fmt: &str) -> Result 61 | where 62 | Self: std::marker::Sized; 63 | 64 | /// Returns a result containing the sysctl name on success, or a 65 | /// SysctlError on failure. 66 | /// 67 | /// # Example 68 | /// ``` 69 | /// # use sysctl::Sysctl; 70 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.ostype") { 71 | /// assert_eq!(ctl.name().unwrap(), "kern.ostype"); 72 | /// } 73 | /// ``` 74 | fn name(&self) -> Result; 75 | 76 | /// Returns a result containing the sysctl value type on success, 77 | /// or a Sysctl Error on failure. 78 | /// 79 | /// # Example 80 | /// 81 | /// ``` 82 | /// # use sysctl::Sysctl; 83 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.ostype") { 84 | /// let value_type = ctl.value_type().unwrap(); 85 | /// assert_eq!(value_type, sysctl::CtlType::String); 86 | /// } 87 | /// ``` 88 | fn value_type(&self) -> Result; 89 | 90 | /// Returns a result containing the sysctl description if success, or an 91 | /// Error on failure. 92 | /// 93 | /// # Example 94 | /// ``` 95 | /// # use sysctl::Sysctl; 96 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.ostype") { 97 | /// println!("Description: {:?}", ctl.description()) 98 | /// } 99 | /// ``` 100 | fn description(&self) -> Result; 101 | 102 | /// Returns a result containing the sysctl value on success, or a 103 | /// SysctlError on failure. 104 | /// 105 | /// # Example 106 | /// ``` 107 | /// # use sysctl::Sysctl; 108 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.ostype") { 109 | /// println!("Value: {:?}", ctl.value()); 110 | /// } 111 | /// ``` 112 | fn value(&self) -> Result; 113 | 114 | /// A generic method that takes returns a result containing the sysctl 115 | /// value if success, or a SysctlError on failure. 116 | /// 117 | /// May only be called for sysctls of type Opaque or Struct. 118 | /// # Example 119 | /// ``` 120 | /// # use sysctl::Sysctl; 121 | /// #[derive(Debug)] 122 | /// #[repr(C)] 123 | /// struct ClockInfo { 124 | /// hz: libc::c_int, /* clock frequency */ 125 | /// tick: libc::c_int, /* micro-seconds per hz tick */ 126 | /// spare: libc::c_int, 127 | /// stathz: libc::c_int, /* statistics clock frequency */ 128 | /// profhz: libc::c_int, /* profiling clock frequency */ 129 | /// } 130 | /// 131 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.clockrate") { 132 | /// println!("{:?}", ctl.value_as::()); 133 | /// } 134 | /// ``` 135 | fn value_as(&self) -> Result, SysctlError>; 136 | 137 | /// Returns a result containing the sysctl value as String on 138 | /// success, or a SysctlError on failure. 139 | /// 140 | /// # Example 141 | /// ``` 142 | /// # use sysctl::Sysctl; 143 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.osrevision") { 144 | /// println!("Value: {:?}", ctl.value_string()); 145 | /// } 146 | /// ``` 147 | fn value_string(&self) -> Result; 148 | 149 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::needless_doctest_main))] 150 | /// Sets the value of a sysctl. 151 | /// Fetches and returns the new value if successful, or returns a 152 | /// SysctlError on failure. 153 | /// # Example 154 | /// ``` 155 | /// use sysctl::Sysctl; 156 | /// 157 | /// fn main() { 158 | /// if unsafe { libc::getuid() } == 0 { 159 | /// if let Ok(ctl) = sysctl::Ctl::new("hw.usb.debug") { 160 | /// let org = ctl.value().unwrap(); 161 | /// let set = ctl.set_value(sysctl::CtlValue::Int(1)).unwrap(); 162 | /// assert_eq!(set, sysctl::CtlValue::Int(1)); 163 | /// ctl.set_value(org).unwrap(); 164 | /// } 165 | /// } 166 | /// } 167 | fn set_value(&self, value: CtlValue) -> Result; 168 | 169 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::needless_doctest_main))] 170 | /// Sets the value of a sysctl with input as string. 171 | /// Fetches and returns the new value if successful, or returns a 172 | /// SysctlError on failure. 173 | /// # Example 174 | /// ``` 175 | /// use sysctl::Sysctl; 176 | /// 177 | /// fn main() { 178 | /// if unsafe { libc::getuid() } == 0 { 179 | /// if let Ok(ctl) = sysctl::Ctl::new("hw.usb.debug") { 180 | /// let org = ctl.value_string().unwrap(); 181 | /// let set = ctl.set_value_string("1"); 182 | /// println!("hw.usb.debug: -> {:?}", set); 183 | /// ctl.set_value_string(&org).unwrap(); 184 | /// } 185 | /// } 186 | /// } 187 | fn set_value_string(&self, value: &str) -> Result; 188 | 189 | /// Get the flags for a sysctl. 190 | /// 191 | /// Returns a Result containing the flags on success, 192 | /// or a SysctlError on failure. 193 | /// 194 | /// # Example 195 | /// ``` 196 | /// # use sysctl::Sysctl; 197 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.ostype") { 198 | /// let readable = ctl.flags().unwrap().contains(sysctl::CtlFlags::RD); 199 | /// assert!(readable); 200 | /// } 201 | /// ``` 202 | fn flags(&self) -> Result; 203 | 204 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::needless_doctest_main))] 205 | /// Returns a Result containing the control metadata for a sysctl. 206 | /// 207 | /// Returns a Result containing the CtlInfo struct on success, 208 | /// or a SysctlError on failure. 209 | /// 210 | /// # Example 211 | /// ``` 212 | /// use sysctl::Sysctl; 213 | /// 214 | /// fn main() { 215 | /// if let Ok(ctl) = sysctl::Ctl::new("kern.osrevision") { 216 | /// let info = ctl.info().unwrap(); 217 | /// 218 | /// // kern.osrevision is not a structure. 219 | /// assert_eq!(info.struct_type(), None); 220 | /// } 221 | /// } 222 | /// ``` 223 | fn info(&self) -> Result; 224 | } 225 | -------------------------------------------------------------------------------- /src/unix/ctl.rs: -------------------------------------------------------------------------------- 1 | // unix/ctl.rs 2 | 3 | use super::funcs::*; 4 | use ctl_error::SysctlError; 5 | use ctl_flags::CtlFlags; 6 | use ctl_info::CtlInfo; 7 | use ctl_type::CtlType; 8 | use ctl_value::CtlValue; 9 | use std::str::FromStr; 10 | use traits::Sysctl; 11 | 12 | /// This struct represents a system control. 13 | #[derive(Debug, Clone, PartialEq)] 14 | pub enum Ctl { 15 | Oid(Vec), 16 | Name(String, CtlType, String), 17 | } 18 | 19 | impl Ctl { 20 | pub fn oid(&self) -> Option<&Vec> { 21 | match self { 22 | Ctl::Oid(oid) => Some(oid), 23 | _ => None, 24 | } 25 | } 26 | } 27 | 28 | impl std::str::FromStr for Ctl { 29 | type Err = SysctlError; 30 | 31 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::redundant_field_names))] 32 | fn from_str(s: &str) -> Result { 33 | let oid = name2oid(s)?; 34 | 35 | Ok(Ctl::Oid(oid)) 36 | } 37 | } 38 | 39 | impl Sysctl for Ctl { 40 | fn new(name: &str) -> Result { 41 | Ctl::from_str(name) 42 | } 43 | 44 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 45 | fn new_with_type(name: &str, _ctl_type: CtlType, _fmt: &str) -> Result { 46 | Ctl::from_str(name) 47 | } 48 | 49 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 50 | fn new_with_type(name: &str, ctl_type: CtlType, fmt: &str) -> Result { 51 | let _ = name2oid(name)?; 52 | 53 | Ok(Ctl::Name(name.to_string(), ctl_type, fmt.to_string())) 54 | } 55 | 56 | fn name(&self) -> Result { 57 | match self { 58 | Ctl::Oid(oid) => oid2name(&oid), 59 | Ctl::Name(name, ..) => Ok(name.clone()), 60 | } 61 | } 62 | 63 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 64 | fn value_type(&self) -> Result { 65 | match self { 66 | Ctl::Oid(oid) => { 67 | let info = oidfmt(oid)?; 68 | Ok(info.ctl_type) 69 | } 70 | Ctl::Name(_, ctl_type, _) => { 71 | Ok(*ctl_type) 72 | } 73 | } 74 | } 75 | 76 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 77 | fn value_type(&self) -> Result { 78 | match self { 79 | Ctl::Oid(oid) => { 80 | let info = oidfmt(oid)?; 81 | 82 | Ok(match info.ctl_type { 83 | CtlType::Int => match info.fmt.as_str() { 84 | "I" => CtlType::Int, 85 | "IU" => CtlType::Uint, 86 | "L" => CtlType::Long, 87 | "LU" => CtlType::Ulong, 88 | _ => return Err(SysctlError::MissingImplementation), 89 | }, 90 | ctl_type => ctl_type, 91 | }) 92 | } 93 | Ctl::Name(_, ctl_type, fmt) => { 94 | Ok(match ctl_type { 95 | CtlType::Int => match fmt.as_str() { 96 | "I" => CtlType::Int, 97 | "IU" => CtlType::Uint, 98 | "L" => CtlType::Long, 99 | "LU" => CtlType::Ulong, 100 | _ => return Err(SysctlError::MissingImplementation), 101 | }, 102 | ctl_type => *ctl_type, 103 | }) 104 | } 105 | } 106 | } 107 | 108 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 109 | fn description(&self) -> Result { 110 | let oid = self.oid().ok_or(SysctlError::MissingImplementation)?; 111 | oid2description(oid) 112 | } 113 | 114 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 115 | fn description(&self) -> Result { 116 | Ok("[N/A]".to_string()) 117 | } 118 | 119 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 120 | fn value(&self) -> Result { 121 | let oid = self.oid().ok_or(SysctlError::MissingImplementation)?; 122 | value_oid(oid) 123 | } 124 | 125 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 126 | fn value(&self) -> Result { 127 | match self { 128 | Ctl::Oid(oid) => { 129 | let mut oid = oid.clone(); 130 | value_oid(&mut oid) 131 | } 132 | Ctl::Name(name, ctl_type, fmt) => { 133 | value_name(name.as_str(), *ctl_type, fmt.as_str()) 134 | } 135 | } 136 | } 137 | 138 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 139 | fn value_as(&self) -> Result, SysctlError> { 140 | let oid = self.oid().ok_or(SysctlError::MissingImplementation)?; 141 | value_oid_as::(oid) 142 | } 143 | 144 | fn value_string(&self) -> Result { 145 | self.value().map(|v| format!("{}", v)) 146 | } 147 | 148 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 149 | fn value_as(&self) -> Result, SysctlError> { 150 | match self { 151 | Ctl::Oid(oid) => { 152 | let mut oid = oid.clone(); 153 | value_oid_as::(&mut oid) 154 | } 155 | Ctl::Name(name, ctl_type, fmt) => { 156 | value_name_as::(name.as_str(), *ctl_type, fmt.as_str()) 157 | } 158 | } 159 | } 160 | 161 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 162 | fn set_value(&self, value: CtlValue) -> Result { 163 | let oid = self.oid().ok_or(SysctlError::MissingImplementation)?; 164 | set_oid_value(&oid, value) 165 | } 166 | 167 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 168 | fn set_value(&self, value: CtlValue) -> Result { 169 | match self { 170 | Ctl::Oid(oid) => { 171 | let mut oid = oid.clone(); 172 | set_oid_value(&mut oid, value) 173 | } 174 | Ctl::Name(name, ctl_type, fmt) => { 175 | set_name_value(name.as_str(), *ctl_type, fmt.as_str(), value) 176 | } 177 | } 178 | } 179 | 180 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 181 | fn set_value_string(&self, value: &str) -> Result { 182 | let oid = self.oid().ok_or(SysctlError::MissingImplementation)?; 183 | let ctl_type = self.value_type()?; 184 | let _ = match ctl_type { 185 | CtlType::String => set_oid_value(oid, CtlValue::String(value.to_owned())), 186 | CtlType::Uint => set_oid_value( 187 | oid, 188 | CtlValue::Uint(value.parse::().map_err(|_| SysctlError::ParseError)?), 189 | ), 190 | CtlType::Int => set_oid_value( 191 | oid, 192 | CtlValue::Int(value.parse::().map_err(|_| SysctlError::ParseError)?), 193 | ), 194 | CtlType::Ulong => set_oid_value( 195 | oid, 196 | CtlValue::Ulong(value.parse::().map_err(|_| SysctlError::ParseError)?), 197 | ), 198 | CtlType::U8 => set_oid_value( 199 | oid, 200 | CtlValue::U8(value.parse::().map_err(|_| SysctlError::ParseError)?), 201 | ), 202 | _ => Err(SysctlError::MissingImplementation), 203 | }?; 204 | self.value_string() 205 | } 206 | 207 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 208 | fn set_value_string(&self, value: &str) -> Result { 209 | let ctl_type = self.value_type()?; 210 | 211 | match self { 212 | Ctl::Oid(oid) => { 213 | let mut oid = oid.clone(); 214 | let _ = match ctl_type { 215 | CtlType::String => set_oid_value(&mut oid, CtlValue::String(value.to_owned())), 216 | CtlType::Uint => set_oid_value( 217 | &mut oid, 218 | CtlValue::Uint(value.parse::().map_err(|_| SysctlError::ParseError)?), 219 | ), 220 | CtlType::Int => set_oid_value( 221 | &mut oid, 222 | CtlValue::Int(value.parse::().map_err(|_| SysctlError::ParseError)?), 223 | ), 224 | CtlType::Ulong => set_oid_value( 225 | &mut oid, 226 | CtlValue::Ulong(value.parse::().map_err(|_| SysctlError::ParseError)?), 227 | ), 228 | CtlType::U8 => set_oid_value( 229 | &mut oid, 230 | CtlValue::U8(value.parse::().map_err(|_| SysctlError::ParseError)?), 231 | ), 232 | _ => Err(SysctlError::MissingImplementation), 233 | }?; 234 | } 235 | Ctl::Name(name, ctl_type, fmt) => { 236 | let _ = match ctl_type { 237 | CtlType::String => set_name_value( 238 | name.as_str(), 239 | *ctl_type, 240 | fmt.as_str(), 241 | CtlValue::String(value.to_owned()), 242 | ), 243 | CtlType::Uint => set_name_value( 244 | name.as_str(), 245 | *ctl_type, 246 | fmt.as_str(), 247 | CtlValue::Uint(value.parse::().map_err(|_| SysctlError::ParseError)?), 248 | ), 249 | CtlType::Int => set_name_value( 250 | name.as_str(), 251 | *ctl_type, 252 | fmt.as_str(), 253 | CtlValue::Int(value.parse::().map_err(|_| SysctlError::ParseError)?), 254 | ), 255 | CtlType::Ulong => set_name_value( 256 | name.as_str(), 257 | *ctl_type, 258 | fmt.as_str(), 259 | CtlValue::Ulong(value.parse::().map_err(|_| SysctlError::ParseError)?), 260 | ), 261 | CtlType::U8 => set_name_value( 262 | name.as_str(), 263 | *ctl_type, 264 | fmt.as_str(), 265 | CtlValue::U8(value.parse::().map_err(|_| SysctlError::ParseError)?), 266 | ), 267 | _ => Err(SysctlError::MissingImplementation), 268 | }?; 269 | } 270 | } 271 | 272 | self.value_string() 273 | } 274 | 275 | fn flags(&self) -> Result { 276 | Ok(self.info()?.flags()) 277 | } 278 | 279 | fn info(&self) -> Result { 280 | let oid = self.oid().ok_or(SysctlError::MissingImplementation)?; 281 | oidfmt(oid) 282 | } 283 | } 284 | 285 | #[cfg(test)] 286 | mod tests { 287 | use crate::Sysctl; 288 | 289 | #[test] 290 | fn ctl_new() { 291 | let _ = super::Ctl::new("kern.ostype").expect("Ctl::new"); 292 | } 293 | 294 | #[test] 295 | fn ctl_description() { 296 | let ctl = super::Ctl::new("kern.ostype").expect("Ctl::new"); 297 | 298 | let descp = ctl.description(); 299 | assert!(descp.is_ok()); 300 | 301 | let descp = descp.unwrap(); 302 | 303 | #[cfg(target_os = "freebsd")] 304 | assert_eq!(descp, "Operating system type"); 305 | 306 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos", target_os = "linux"))] 307 | assert_eq!(descp, "[N/A]"); 308 | } 309 | } 310 | 311 | #[cfg(all(test, target_os = "freebsd"))] 312 | mod tests_freebsd {} 313 | -------------------------------------------------------------------------------- /src/unix/ctl_iter.rs: -------------------------------------------------------------------------------- 1 | // unix/ctl_iter.rs 2 | 3 | use super::ctl::Ctl; 4 | use super::funcs::next_oid; 5 | use ctl_error::SysctlError; 6 | 7 | /// An iterator over Sysctl entries. 8 | pub struct CtlIter { 9 | // if we are iterating over a Node, only include OIDs 10 | // starting with this base. Set to None if iterating over all 11 | // OIDs. 12 | base: Ctl, 13 | current: Ctl, 14 | } 15 | 16 | impl CtlIter { 17 | /// Return an iterator over the complete sysctl tree. 18 | pub fn root() -> Self { 19 | CtlIter { 20 | base: Ctl::Oid(vec![]), 21 | current: Ctl::Oid(vec![1]), 22 | } 23 | } 24 | 25 | /// Return an iterator over all sysctl entries below the given node. 26 | pub fn below(node: Ctl) -> Self { 27 | CtlIter { 28 | base: node.clone(), 29 | current: node, 30 | } 31 | } 32 | } 33 | 34 | impl Iterator for CtlIter { 35 | type Item = Result; 36 | 37 | fn next(&mut self) -> Option { 38 | let oid = match next_oid(self.current.oid()?) { 39 | Ok(Some(o)) => o, 40 | Err(e) => return Some(Err(e)), 41 | Ok(None) => return None, 42 | }; 43 | 44 | // We continue iterating as long as the oid starts with the base 45 | let cont = oid.starts_with(self.base.oid()?); 46 | 47 | self.current = Ctl::Oid(oid); 48 | 49 | match cont { 50 | true => Some(Ok(self.current.clone())), 51 | false => None, 52 | } 53 | } 54 | } 55 | 56 | /// Ctl implements the IntoIterator trait to allow for easy iteration 57 | /// over nodes. 58 | /// 59 | /// # Example 60 | /// 61 | /// ``` 62 | /// use sysctl::Sysctl; 63 | /// 64 | /// let kern = sysctl::Ctl::new("kern"); 65 | /// for ctl in kern { 66 | /// println!("{}", ctl.name().unwrap()); 67 | /// } 68 | /// ``` 69 | impl IntoIterator for Ctl { 70 | type Item = Result; 71 | type IntoIter = CtlIter; 72 | 73 | fn into_iter(self) -> Self::IntoIter { 74 | CtlIter::below(self) 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use crate::Sysctl; 81 | 82 | #[test] 83 | fn ctl_iter_iterate_all() { 84 | let root = super::CtlIter::root(); 85 | let all_ctls: Vec = root.into_iter().filter_map(Result::ok).collect(); 86 | assert_ne!(all_ctls.len(), 0); 87 | for ctl in &all_ctls { 88 | println!("{:?}", ctl.name()); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/unix/funcs.rs: -------------------------------------------------------------------------------- 1 | // unix/funcs.rs 2 | 3 | use byteorder::{ByteOrder, WriteBytesExt}; 4 | use consts::*; 5 | use ctl_error::*; 6 | use ctl_info::*; 7 | use ctl_type::*; 8 | use ctl_value::*; 9 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 10 | use std::ffi::CString; 11 | 12 | #[cfg(target_os = "freebsd")] 13 | use temperature::*; 14 | 15 | pub fn name2oid(name: &str) -> Result, SysctlError> { 16 | // We get results in this vector 17 | let mut len: usize = CTL_MAXNAME as usize; 18 | let mut res = Vec::::with_capacity(len); 19 | let rcname = std::ffi::CString::new(name); 20 | if rcname.is_err() { 21 | return Err(SysctlError::NotFound(name.to_owned())); 22 | } 23 | let cname = rcname.unwrap(); 24 | 25 | let ret = unsafe { 26 | libc::sysctlnametomib( 27 | cname.as_ptr() as *const libc::c_char, 28 | res.as_mut_ptr(), 29 | &mut len 30 | ) 31 | }; 32 | if ret < 0 { 33 | let e = std::io::Error::last_os_error(); 34 | return Err(match e.kind() { 35 | std::io::ErrorKind::NotFound => SysctlError::NotFound(name.into()), 36 | _ => SysctlError::IoError(e), 37 | }); 38 | } else { 39 | unsafe { res.set_len(len); } 40 | } 41 | 42 | Ok(res) 43 | } 44 | 45 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 46 | pub fn oidfmt(oid: &[libc::c_int]) -> Result { 47 | // Request command for type info 48 | let mut qoid: Vec = vec![0, 4]; 49 | qoid.extend(oid); 50 | 51 | // Store results here 52 | let mut buf: [libc::c_uchar; libc::BUFSIZ as usize] = [0; libc::BUFSIZ as usize]; 53 | let mut buf_len = std::mem::size_of_val(&buf); 54 | let ret = unsafe { 55 | libc::sysctl( 56 | qoid.as_ptr(), 57 | qoid.len() as u32, 58 | buf.as_mut_ptr() as *mut libc::c_void, 59 | &mut buf_len, 60 | std::ptr::null(), 61 | 0, 62 | ) 63 | }; 64 | if ret != 0 { 65 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 66 | } 67 | 68 | // 'Kind' is the first 32 bits of result buffer 69 | let kind = byteorder::LittleEndian::read_u32(&buf); 70 | 71 | // 'Type' is the first 4 bits of 'Kind' 72 | let ctltype_val = kind & CTLTYPE as u32; 73 | 74 | // 'fmt' is after 'Kind' in result buffer 75 | let fmt: String = 76 | match std::ffi::CStr::from_bytes_with_nul(&buf[std::mem::size_of::()..buf_len]) { 77 | Ok(x) => x.to_string_lossy().into(), 78 | Err(e) => return Err(SysctlError::InvalidCStr(e)), 79 | }; 80 | 81 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::redundant_field_names))] 82 | let s = CtlInfo { 83 | ctl_type: CtlType::from(ctltype_val), 84 | fmt: fmt, 85 | flags: kind, 86 | }; 87 | Ok(s) 88 | } 89 | 90 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 91 | pub fn oidfmt(oid: &[libc::c_int]) -> Result { 92 | // Request command for type info 93 | let mut qoid: Vec = vec![0, 4]; 94 | qoid.extend(oid); 95 | 96 | // Store results here 97 | let mut buf: [libc::c_uchar; libc::BUFSIZ as usize] = [0; libc::BUFSIZ as usize]; 98 | let mut buf_len = std::mem::size_of_val(&buf); 99 | let ret = unsafe { 100 | libc::sysctl( 101 | qoid.as_mut_ptr(), 102 | qoid.len() as u32, 103 | buf.as_mut_ptr() as *mut libc::c_void, 104 | &mut buf_len, 105 | std::ptr::null_mut(), 106 | 0, 107 | ) 108 | }; 109 | if ret != 0 { 110 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 111 | } 112 | 113 | // 'Kind' is the first 32 bits of result buffer 114 | let kind = byteorder::LittleEndian::read_u32(&buf); 115 | 116 | // 'Type' is the first 4 bits of 'Kind' 117 | let ctltype_val = kind & CTLTYPE as u32; 118 | 119 | // 'fmt' is after 'Kind' in result buffer 120 | let bytes = &buf[std::mem::size_of::()..buf_len]; 121 | 122 | // Ensure we drop the '\0' byte if there are any. 123 | let len = match bytes.iter().position(|c| *c == 0) { 124 | Some(index) => index, 125 | _ => bytes.len(), 126 | }; 127 | 128 | let fmt: String = match std::str::from_utf8(&bytes[..len]) { 129 | Ok(x) => x.to_owned(), 130 | Err(e) => return Err(SysctlError::Utf8Error(e)), 131 | }; 132 | 133 | let s = CtlInfo { 134 | ctl_type: CtlType::from(ctltype_val), 135 | fmt: fmt, 136 | flags: kind, 137 | }; 138 | Ok(s) 139 | } 140 | 141 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 142 | pub fn value_oid(oid: &[i32]) -> Result { 143 | let info: CtlInfo = oidfmt(&oid)?; 144 | 145 | // Check if the value is readable 146 | if info.flags & CTLFLAG_RD != CTLFLAG_RD { 147 | return Err(SysctlError::NoReadAccess); 148 | } 149 | 150 | // First get size of value in bytes 151 | let mut val_len = 0; 152 | let ret = unsafe { 153 | libc::sysctl( 154 | oid.as_ptr(), 155 | oid.len() as u32, 156 | std::ptr::null_mut(), 157 | &mut val_len, 158 | std::ptr::null(), 159 | 0, 160 | ) 161 | }; 162 | if ret < 0 { 163 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 164 | } 165 | 166 | // If the length reported is shorter than the type we will convert it into, 167 | // byteorder::LittleEndian::read_* will panic. Therefore, expand the value length to at 168 | // Least the size of the value. 169 | let val_minsize = std::cmp::max(val_len, info.ctl_type.min_type_size()); 170 | 171 | // Then get value 172 | let mut val: Vec = vec![0; val_minsize]; 173 | let mut new_val_len = val_len; 174 | let ret = unsafe { 175 | libc::sysctl( 176 | oid.as_ptr(), 177 | oid.len() as u32, 178 | val.as_mut_ptr() as *mut libc::c_void, 179 | &mut new_val_len, 180 | std::ptr::null(), 181 | 0, 182 | ) 183 | }; 184 | if ret < 0 { 185 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 186 | } 187 | 188 | // Confirm that we did not read out of bounds 189 | assert!(new_val_len <= val_len); 190 | // Confirm that we got the bytes we requested 191 | if new_val_len < val_len { 192 | return Err(SysctlError::ShortRead { 193 | read: new_val_len, 194 | reported: val_len, 195 | }); 196 | } 197 | 198 | // Special treatment for temperature ctls. 199 | if info.is_temperature() { 200 | return temperature(&info, &val); 201 | } 202 | 203 | // Wrap in Enum and return 204 | match info.ctl_type { 205 | CtlType::None => Ok(CtlValue::None), 206 | CtlType::Node => Ok(CtlValue::Node(val)), 207 | CtlType::Int => Ok(CtlValue::Int(byteorder::LittleEndian::read_i32(&val))), 208 | CtlType::String => match val.len() { 209 | 0 => Ok(CtlValue::String("".to_string())), 210 | l => std::str::from_utf8(&val[..l - 1]) 211 | .map_err(SysctlError::Utf8Error) 212 | .map(|s| CtlValue::String(s.into())), 213 | }, 214 | CtlType::S64 => Ok(CtlValue::S64(byteorder::LittleEndian::read_i64(&val))), 215 | CtlType::Struct => Ok(CtlValue::Struct(val)), 216 | CtlType::Uint => Ok(CtlValue::Uint(byteorder::LittleEndian::read_u32(&val))), 217 | CtlType::Long => Ok(CtlValue::Long(byteorder::LittleEndian::read_i64(&val))), 218 | CtlType::Ulong => Ok(CtlValue::Ulong(byteorder::LittleEndian::read_u64(&val))), 219 | CtlType::U64 => Ok(CtlValue::U64(byteorder::LittleEndian::read_u64(&val))), 220 | CtlType::U8 => Ok(CtlValue::U8(val[0])), 221 | CtlType::U16 => Ok(CtlValue::U16(byteorder::LittleEndian::read_u16(&val))), 222 | CtlType::S8 => Ok(CtlValue::S8(val[0] as i8)), 223 | CtlType::S16 => Ok(CtlValue::S16(byteorder::LittleEndian::read_i16(&val))), 224 | CtlType::S32 => Ok(CtlValue::S32(byteorder::LittleEndian::read_i32(&val))), 225 | CtlType::U32 => Ok(CtlValue::U32(byteorder::LittleEndian::read_u32(&val))), 226 | _ => Err(SysctlError::UnknownType), 227 | } 228 | } 229 | 230 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 231 | pub fn value_oid(oid: &mut Vec) -> Result { 232 | let info: CtlInfo = oidfmt(&oid)?; 233 | 234 | // Check if the value is readable 235 | if !(info.flags & CTLFLAG_RD == CTLFLAG_RD) { 236 | return Err(SysctlError::NoReadAccess); 237 | } 238 | 239 | // First get size of value in bytes 240 | let mut val_len = 0; 241 | let ret = unsafe { 242 | libc::sysctl( 243 | oid.as_mut_ptr(), 244 | oid.len() as u32, 245 | std::ptr::null_mut(), 246 | &mut val_len, 247 | std::ptr::null_mut(), 248 | 0, 249 | ) 250 | }; 251 | if ret < 0 { 252 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 253 | } 254 | 255 | // If the length reported is shorter than the type we will convert it into, 256 | // byteorder::LittleEndian::read_* will panic. Therefore, expand the value length to at 257 | // Least the size of the value. 258 | let val_minsize = std::cmp::max(val_len, info.ctl_type.min_type_size()); 259 | 260 | // Then get value 261 | let mut val: Vec = vec![0; val_minsize]; 262 | let mut new_val_len = val_len; 263 | let ret = unsafe { 264 | libc::sysctl( 265 | oid.as_mut_ptr(), 266 | oid.len() as u32, 267 | val.as_mut_ptr() as *mut libc::c_void, 268 | &mut new_val_len, 269 | std::ptr::null_mut(), 270 | 0, 271 | ) 272 | }; 273 | if ret < 0 { 274 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 275 | } 276 | 277 | // Confirm that we did not read out of bounds 278 | assert!(new_val_len <= val_len); 279 | // The call can sometimes return bytes that are smaller than initially indicated, so it should 280 | // be safe to truncate it. See a similar approach in golang module: 281 | // https://github.com/golang/sys/blob/43e60d72a8e2bd92ee98319ba9a384a0e9837c08/unix/syscall_bsd.go#L545-L548 282 | if new_val_len < val_len { 283 | val.truncate(new_val_len); 284 | } 285 | 286 | // Wrap in Enum and return 287 | match info.ctl_type { 288 | CtlType::None => Ok(CtlValue::None), 289 | CtlType::Node => Ok(CtlValue::Node(val)), 290 | CtlType::Int => match info.fmt.as_str() { 291 | "I" => Ok(CtlValue::Int(byteorder::LittleEndian::read_i32(&val))), 292 | "IU" => Ok(CtlValue::Uint(byteorder::LittleEndian::read_u32(&val))), 293 | "L" => Ok(CtlValue::Long(byteorder::LittleEndian::read_i64(&val))), 294 | "LU" => Ok(CtlValue::Ulong(byteorder::LittleEndian::read_u64(&val))), 295 | _ => Ok(CtlValue::None), 296 | } 297 | CtlType::String => match val.len() { 298 | 0 => Ok(CtlValue::String("".to_string())), 299 | l => std::str::from_utf8(&val[..l - 1]) 300 | .map_err(SysctlError::Utf8Error) 301 | .map(|s| CtlValue::String(s.into())), 302 | }, 303 | CtlType::S64 => Ok(CtlValue::S64(byteorder::LittleEndian::read_i64(&val))), 304 | CtlType::Struct => Ok(CtlValue::Struct(val)), 305 | CtlType::Uint => Ok(CtlValue::Uint(byteorder::LittleEndian::read_u32(&val))), 306 | CtlType::Long => Ok(CtlValue::Long(byteorder::LittleEndian::read_i64(&val))), 307 | CtlType::Ulong => Ok(CtlValue::Ulong(byteorder::LittleEndian::read_u64(&val))), 308 | CtlType::U64 => Ok(CtlValue::U64(byteorder::LittleEndian::read_u64(&val))), 309 | CtlType::U8 => Ok(CtlValue::U8(val[0])), 310 | CtlType::U16 => Ok(CtlValue::U16(byteorder::LittleEndian::read_u16(&val))), 311 | CtlType::S8 => Ok(CtlValue::S8(val[0] as i8)), 312 | CtlType::S16 => Ok(CtlValue::S16(byteorder::LittleEndian::read_i16(&val))), 313 | CtlType::S32 => Ok(CtlValue::S32(byteorder::LittleEndian::read_i32(&val))), 314 | CtlType::U32 => Ok(CtlValue::U32(byteorder::LittleEndian::read_u32(&val))), 315 | } 316 | } 317 | 318 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 319 | pub fn value_oid_as(oid: &[i32]) -> Result, SysctlError> { 320 | let val_enum = value_oid(oid)?; 321 | 322 | // Some structs are apparently reported as Node so this check is invalid.. 323 | // let ctl_type = CtlType::from(&val_enum); 324 | // assert_eq!(CtlType::Struct, ctl_type, "Error type is not struct/opaque"); 325 | 326 | // TODO: refactor this when we have better clue to what's going on 327 | if let CtlValue::Struct(val) = val_enum { 328 | // Make sure we got correct data size 329 | assert_eq!( 330 | std::mem::size_of::(), 331 | val.len(), 332 | "Error memory size mismatch. Size of struct {}, size of data retrieved {}.", 333 | std::mem::size_of::(), 334 | val.len() 335 | ); 336 | 337 | // val is Vec 338 | let val_array: Box<[u8]> = val.into_boxed_slice(); 339 | let val_raw: *mut T = Box::into_raw(val_array) as *mut T; 340 | let val_box: Box = unsafe { Box::from_raw(val_raw) }; 341 | Ok(val_box) 342 | } else if let CtlValue::Node(val) = val_enum { 343 | // Make sure we got correct data size 344 | assert_eq!( 345 | std::mem::size_of::(), 346 | val.len(), 347 | "Error memory size mismatch. Size of struct {}, size of data retrieved {}.", 348 | std::mem::size_of::(), 349 | val.len() 350 | ); 351 | 352 | // val is Vec 353 | let val_array: Box<[u8]> = val.into_boxed_slice(); 354 | let val_raw: *mut T = Box::into_raw(val_array) as *mut T; 355 | let val_box: Box = unsafe { Box::from_raw(val_raw) }; 356 | Ok(val_box) 357 | } else { 358 | Err(SysctlError::ExtractionError) 359 | } 360 | } 361 | 362 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 363 | pub fn value_oid_as(oid: &mut Vec) -> Result, SysctlError> { 364 | let val_enum = value_oid(oid)?; 365 | 366 | // Some structs are apparently reported as Node so this check is invalid.. 367 | // let ctl_type = CtlType::from(&val_enum); 368 | // assert_eq!(CtlType::Struct, ctl_type, "Error type is not struct/opaque"); 369 | 370 | // TODO: refactor this when we have better clue to what's going on 371 | if let CtlValue::Struct(val) = val_enum { 372 | // Make sure we got correct data size 373 | assert_eq!( 374 | std::mem::size_of::(), 375 | val.len(), 376 | "Error memory size mismatch. Size of struct {}, size of data retrieved {}.", 377 | std::mem::size_of::(), 378 | val.len() 379 | ); 380 | 381 | // val is Vec 382 | let val_array: Box<[u8]> = val.into_boxed_slice(); 383 | let val_raw: *mut T = Box::into_raw(val_array) as *mut T; 384 | let val_box: Box = unsafe { Box::from_raw(val_raw) }; 385 | Ok(val_box) 386 | } else if let CtlValue::Node(val) = val_enum { 387 | // Make sure we got correct data size 388 | assert_eq!( 389 | std::mem::size_of::(), 390 | val.len(), 391 | "Error memory size mismatch. Size of struct {}, size of data retrieved {}.", 392 | std::mem::size_of::(), 393 | val.len() 394 | ); 395 | 396 | // val is Vec 397 | let val_array: Box<[u8]> = val.into_boxed_slice(); 398 | let val_raw: *mut T = Box::into_raw(val_array) as *mut T; 399 | let val_box: Box = unsafe { Box::from_raw(val_raw) }; 400 | Ok(val_box) 401 | } else { 402 | Err(SysctlError::ExtractionError) 403 | } 404 | } 405 | 406 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 407 | pub fn value_name(name: &str, ctl_type: CtlType, fmt: &str) -> Result { 408 | let name = CString::new(name)?; 409 | 410 | // First get size of value in bytes 411 | let mut val_len = 0; 412 | let ret = unsafe { 413 | libc::sysctlbyname( 414 | name.as_ptr(), 415 | std::ptr::null_mut(), 416 | &mut val_len, 417 | std::ptr::null_mut(), 418 | 0, 419 | ) 420 | }; 421 | if ret < 0 { 422 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 423 | } 424 | 425 | // If the length reported is shorter than the type we will convert it into, 426 | // byteorder::LittleEndian::read_* will panic. Therefore, expand the value length to at 427 | // Least the size of the value. 428 | let val_minsize = std::cmp::max(val_len, ctl_type.min_type_size()); 429 | 430 | // Then get value 431 | let mut val: Vec = vec![0; val_minsize]; 432 | let mut new_val_len = val_len; 433 | let ret = unsafe { 434 | libc::sysctlbyname( 435 | name.as_ptr(), 436 | val.as_mut_ptr() as *mut libc::c_void, 437 | &mut new_val_len, 438 | std::ptr::null_mut(), 439 | 0, 440 | ) 441 | }; 442 | if ret < 0 { 443 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 444 | } 445 | 446 | // Confirm that we did not read out of bounds 447 | assert!(new_val_len <= val_len); 448 | // The call can sometimes return bytes that are smaller than initially indicated, so it should 449 | // be safe to truncate it. See a similar approach in golang module: 450 | // https://github.com/golang/sys/blob/43e60d72a8e2bd92ee98319ba9a384a0e9837c08/unix/syscall_bsd.go#L545-L548 451 | if new_val_len < val_len { 452 | val.truncate(new_val_len); 453 | } 454 | 455 | // Wrap in Enum and return 456 | match ctl_type { 457 | CtlType::None => Ok(CtlValue::None), 458 | CtlType::Node => Ok(CtlValue::Node(val)), 459 | CtlType::Int => match fmt { 460 | "I" => Ok(CtlValue::Int(byteorder::LittleEndian::read_i32(&val))), 461 | "IU" => Ok(CtlValue::Uint(byteorder::LittleEndian::read_u32(&val))), 462 | "L" => Ok(CtlValue::Long(byteorder::LittleEndian::read_i64(&val))), 463 | "LU" => Ok(CtlValue::Ulong(byteorder::LittleEndian::read_u64(&val))), 464 | _ => Ok(CtlValue::None), 465 | } 466 | CtlType::String => match val.len() { 467 | 0 => Ok(CtlValue::String("".to_string())), 468 | l => std::str::from_utf8(&val[..l - 1]) 469 | .map_err(SysctlError::Utf8Error) 470 | .map(|s| CtlValue::String(s.into())), 471 | }, 472 | CtlType::S64 => Ok(CtlValue::S64(byteorder::LittleEndian::read_i64(&val))), 473 | CtlType::Struct => Ok(CtlValue::Struct(val)), 474 | CtlType::Uint => Ok(CtlValue::Uint(byteorder::LittleEndian::read_u32(&val))), 475 | CtlType::Long => Ok(CtlValue::Long(byteorder::LittleEndian::read_i64(&val))), 476 | CtlType::Ulong => Ok(CtlValue::Ulong(byteorder::LittleEndian::read_u64(&val))), 477 | CtlType::U64 => Ok(CtlValue::U64(byteorder::LittleEndian::read_u64(&val))), 478 | CtlType::U8 => Ok(CtlValue::U8(val[0])), 479 | CtlType::U16 => Ok(CtlValue::U16(byteorder::LittleEndian::read_u16(&val))), 480 | CtlType::S8 => Ok(CtlValue::S8(val[0] as i8)), 481 | CtlType::S16 => Ok(CtlValue::S16(byteorder::LittleEndian::read_i16(&val))), 482 | CtlType::S32 => Ok(CtlValue::S32(byteorder::LittleEndian::read_i32(&val))), 483 | CtlType::U32 => Ok(CtlValue::U32(byteorder::LittleEndian::read_u32(&val))), 484 | } 485 | } 486 | 487 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 488 | pub fn value_name_as(name: &str, ctl_type: CtlType, fmt: &str) -> Result, SysctlError> { 489 | let val_enum = value_name(name, ctl_type, fmt)?; 490 | 491 | // Some structs are apparently reported as Node so this check is invalid.. 492 | // let ctl_type = CtlType::from(&val_enum); 493 | // assert_eq!(CtlType::Struct, ctl_type, "Error type is not struct/opaque"); 494 | 495 | // TODO: refactor this when we have better clue to what's going on 496 | if let CtlValue::Struct(val) = val_enum { 497 | // Make sure we got correct data size 498 | assert_eq!( 499 | std::mem::size_of::(), 500 | val.len(), 501 | "Error memory size mismatch. Size of struct {}, size of data retrieved {}.", 502 | std::mem::size_of::(), 503 | val.len() 504 | ); 505 | 506 | // val is Vec 507 | let val_array: Box<[u8]> = val.into_boxed_slice(); 508 | let val_raw: *mut T = Box::into_raw(val_array) as *mut T; 509 | let val_box: Box = unsafe { Box::from_raw(val_raw) }; 510 | Ok(val_box) 511 | } else if let CtlValue::Node(val) = val_enum { 512 | // Make sure we got correct data size 513 | assert_eq!( 514 | std::mem::size_of::(), 515 | val.len(), 516 | "Error memory size mismatch. Size of struct {}, size of data retrieved {}.", 517 | std::mem::size_of::(), 518 | val.len() 519 | ); 520 | 521 | // val is Vec 522 | let val_array: Box<[u8]> = val.into_boxed_slice(); 523 | let val_raw: *mut T = Box::into_raw(val_array) as *mut T; 524 | let val_box: Box = unsafe { Box::from_raw(val_raw) }; 525 | Ok(val_box) 526 | } else { 527 | Err(SysctlError::ExtractionError) 528 | } 529 | } 530 | 531 | fn value_to_bytes(value: CtlValue) -> Result, SysctlError> { 532 | // TODO rest of the types 533 | match value { 534 | CtlValue::String(v) => Ok(v.as_bytes().to_owned()), 535 | CtlValue::Ulong(v) => { 536 | let mut bytes = vec![]; 537 | bytes.write_u64::(v)?; 538 | Ok(bytes) 539 | } 540 | CtlValue::Uint(v) => { 541 | let mut bytes = vec![]; 542 | bytes.write_u32::(v)?; 543 | Ok(bytes) 544 | } 545 | CtlValue::Int(v) => { 546 | let mut bytes = vec![]; 547 | bytes.write_i32::(v)?; 548 | Ok(bytes) 549 | } 550 | CtlValue::U8(v) => { 551 | let mut bytes = vec![]; 552 | bytes.write_u8(v)?; 553 | Ok(bytes) 554 | } 555 | _ => Err(SysctlError::MissingImplementation), 556 | } 557 | } 558 | 559 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 560 | pub fn set_oid_value(oid: &[libc::c_int], value: CtlValue) -> Result { 561 | let info: CtlInfo = oidfmt(&oid)?; 562 | 563 | // Check if the value is writeable 564 | if info.flags & CTLFLAG_WR != CTLFLAG_WR { 565 | return Err(SysctlError::NoWriteAccess); 566 | } 567 | 568 | let ctl_type = CtlType::from(&value); 569 | assert_eq!( 570 | info.ctl_type, ctl_type, 571 | "Error type mismatch. Type given {:?}, sysctl type: {:?}", 572 | ctl_type, info.ctl_type 573 | ); 574 | 575 | let bytes = value_to_bytes(value)?; 576 | 577 | let ret = unsafe { 578 | libc::sysctl( 579 | oid.as_ptr(), 580 | oid.len() as u32, 581 | std::ptr::null_mut(), 582 | std::ptr::null_mut(), 583 | bytes.as_ptr() as *const libc::c_void, 584 | bytes.len(), 585 | ) 586 | }; 587 | if ret < 0 { 588 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 589 | } 590 | 591 | // Get the new value and return for confirmation 592 | self::value_oid(oid) 593 | } 594 | 595 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 596 | pub fn set_oid_value(oid: &mut Vec, value: CtlValue) -> Result { 597 | let info: CtlInfo = oidfmt(&oid)?; 598 | 599 | // Check if the value is writeable 600 | if !(info.flags & CTLFLAG_WR == CTLFLAG_WR) { 601 | return Err(SysctlError::NoWriteAccess); 602 | } 603 | 604 | let ctl_type = CtlType::from(&value); 605 | 606 | // Get the correct ctl type based on the format string 607 | let info_ctl_type = match info.ctl_type { 608 | CtlType::Int => match info.fmt.as_str() { 609 | "I" => CtlType::Int, 610 | "IU" => CtlType::Uint, 611 | "L" => CtlType::Long, 612 | "LU" => CtlType::Ulong, 613 | _ => return Err(SysctlError::MissingImplementation), 614 | } 615 | ctl_type => ctl_type, 616 | }; 617 | 618 | assert_eq!( 619 | info_ctl_type, ctl_type, 620 | "Error type mismatch. Type given {:?}, sysctl type: {:?}", 621 | ctl_type, info_ctl_type 622 | ); 623 | 624 | let bytes = value_to_bytes(value)?; 625 | 626 | // Set value 627 | let ret = unsafe { 628 | libc::sysctl( 629 | oid.as_mut_ptr(), 630 | oid.len() as u32, 631 | std::ptr::null_mut(), 632 | std::ptr::null_mut(), 633 | bytes.as_ptr() as *mut libc::c_void, 634 | bytes.len(), 635 | ) 636 | }; 637 | if ret < 0 { 638 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 639 | } 640 | 641 | // Get the new value and return for confirmation 642 | self::value_oid(oid) 643 | } 644 | 645 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 646 | pub fn set_name_value( 647 | name: &str, 648 | info_ctl_type: CtlType, 649 | fmt: &str, 650 | value: CtlValue, 651 | ) -> Result { 652 | let c_name = CString::new(name)?; 653 | let ctl_type = CtlType::from(&value); 654 | 655 | // Get the correct ctl type based on the format string 656 | let info_ctl_type = match info_ctl_type { 657 | CtlType::Int => match fmt { 658 | "I" => CtlType::Int, 659 | "IU" => CtlType::Uint, 660 | "L" => CtlType::Long, 661 | "LU" => CtlType::Ulong, 662 | _ => return Err(SysctlError::MissingImplementation), 663 | } 664 | ctl_type => ctl_type, 665 | }; 666 | 667 | assert_eq!( 668 | info_ctl_type, ctl_type, 669 | "Error type mismatch. Type given {:?}, sysctl type: {:?}", 670 | ctl_type, info_ctl_type 671 | ); 672 | 673 | let bytes = value_to_bytes(value)?; 674 | 675 | // Set value 676 | let ret = unsafe { 677 | libc::sysctlbyname( 678 | c_name.as_ptr(), 679 | std::ptr::null_mut(), 680 | std::ptr::null_mut(), 681 | bytes.as_ptr() as *mut libc::c_void, 682 | bytes.len(), 683 | ) 684 | }; 685 | if ret < 0 { 686 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 687 | } 688 | 689 | // Get the new value and return for confirmation 690 | self::value_name(name, ctl_type, fmt) 691 | } 692 | 693 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 694 | pub fn oid2description(oid: &[libc::c_int]) -> Result { 695 | // Request command for description 696 | let mut qoid: Vec = vec![0, 5]; 697 | qoid.extend(oid); 698 | 699 | // Store results in u8 array 700 | let mut buf: [libc::c_uchar; libc::BUFSIZ as usize] = [0; libc::BUFSIZ as usize]; 701 | let mut buf_len = std::mem::size_of_val(&buf); 702 | let ret = unsafe { 703 | libc::sysctl( 704 | qoid.as_ptr(), 705 | qoid.len() as u32, 706 | buf.as_mut_ptr() as *mut libc::c_void, 707 | &mut buf_len, 708 | std::ptr::null(), 709 | 0, 710 | ) 711 | }; 712 | if ret != 0 { 713 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 714 | } 715 | 716 | // Use buf_len - 1 so that we remove the trailing NULL 717 | match std::str::from_utf8(&buf[..buf_len - 1]) { 718 | Ok(s) => Ok(s.to_owned()), 719 | Err(e) => Err(SysctlError::Utf8Error(e)), 720 | } 721 | } 722 | 723 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 724 | pub fn oid2name(oid: &[libc::c_int]) -> Result { 725 | // Request command for name 726 | let mut qoid: Vec = vec![0, 1]; 727 | qoid.extend(oid); 728 | 729 | // Store results in u8 array 730 | let mut buf: [libc::c_uchar; libc::BUFSIZ as usize] = [0; libc::BUFSIZ as usize]; 731 | let mut buf_len = std::mem::size_of_val(&buf); 732 | let ret = unsafe { 733 | libc::sysctl( 734 | qoid.as_ptr(), 735 | qoid.len() as u32, 736 | buf.as_mut_ptr() as *mut libc::c_void, 737 | &mut buf_len, 738 | std::ptr::null(), 739 | 0, 740 | ) 741 | }; 742 | if ret != 0 { 743 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 744 | } 745 | 746 | // Use buf_len - 1 so that we remove the trailing NULL 747 | match std::str::from_utf8(&buf[..buf_len - 1]) { 748 | Ok(s) => Ok(s.to_owned()), 749 | Err(e) => Err(SysctlError::Utf8Error(e)), 750 | } 751 | } 752 | 753 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 754 | pub fn oid2name(oid: &Vec) -> Result { 755 | // Request command for name 756 | let mut qoid: Vec = vec![0, 1]; 757 | qoid.extend(oid); 758 | 759 | // Store results in u8 array 760 | let mut buf: [libc::c_uchar; libc::BUFSIZ as usize] = [0; libc::BUFSIZ as usize]; 761 | let mut buf_len = std::mem::size_of_val(&buf); 762 | let ret = unsafe { 763 | libc::sysctl( 764 | qoid.as_mut_ptr(), 765 | qoid.len() as u32, 766 | buf.as_mut_ptr() as *mut libc::c_void, 767 | &mut buf_len, 768 | std::ptr::null_mut(), 769 | 0, 770 | ) 771 | }; 772 | if ret != 0 { 773 | return Err(SysctlError::IoError(std::io::Error::last_os_error())); 774 | } 775 | 776 | // Use buf_len - 1 so that we remove the trailing NULL 777 | match std::str::from_utf8(&buf[..buf_len - 1]) { 778 | Ok(s) => Ok(s.to_owned()), 779 | Err(e) => Err(SysctlError::Utf8Error(e)), 780 | } 781 | } 782 | 783 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 784 | pub fn next_oid(oid: &[libc::c_int]) -> Result>, SysctlError> { 785 | // Request command for next oid 786 | let mut qoid: Vec = vec![0, 2]; 787 | qoid.extend(oid); 788 | 789 | let mut len: usize = CTL_MAXNAME as usize * std::mem::size_of::(); 790 | 791 | // We get results in this vector 792 | let mut res: Vec = vec![0; CTL_MAXNAME as usize]; 793 | 794 | let ret = unsafe { 795 | libc::sysctl( 796 | qoid.as_ptr(), 797 | qoid.len() as u32, 798 | res.as_mut_ptr() as *mut libc::c_void, 799 | &mut len, 800 | std::ptr::null(), 801 | 0, 802 | ) 803 | }; 804 | if ret != 0 { 805 | let e = std::io::Error::last_os_error(); 806 | 807 | if e.raw_os_error() == Some(libc::ENOENT) { 808 | return Ok(None); 809 | } 810 | return Err(SysctlError::IoError(e)); 811 | } 812 | 813 | // len is in bytes, convert to number of libc::c_ints 814 | len /= std::mem::size_of::(); 815 | 816 | // Trim result vector 817 | res.truncate(len); 818 | 819 | Ok(Some(res)) 820 | } 821 | 822 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos"))] 823 | pub fn next_oid(oid: &Vec) -> Result>, SysctlError> { 824 | // Request command for next oid 825 | let mut qoid: Vec = vec![0, 2]; 826 | qoid.extend(oid); 827 | 828 | let mut len: usize = CTL_MAXNAME as usize * std::mem::size_of::(); 829 | 830 | // We get results in this vector 831 | let mut res: Vec = vec![0; CTL_MAXNAME as usize]; 832 | 833 | let ret = unsafe { 834 | libc::sysctl( 835 | qoid.as_mut_ptr(), 836 | qoid.len() as u32, 837 | res.as_mut_ptr() as *mut libc::c_void, 838 | &mut len, 839 | std::ptr::null_mut(), 840 | 0, 841 | ) 842 | }; 843 | if ret != 0 { 844 | let e = std::io::Error::last_os_error(); 845 | 846 | if e.raw_os_error() == Some(libc::ENOENT) { 847 | return Ok(None); 848 | } 849 | return Err(SysctlError::IoError(e)); 850 | } 851 | 852 | // len is in bytes, convert to number of libc::c_ints 853 | len /= std::mem::size_of::(); 854 | 855 | // Trim result vector 856 | res.truncate(len); 857 | 858 | Ok(Some(res)) 859 | } 860 | 861 | #[cfg(test)] 862 | mod tests { 863 | use crate::Sysctl; 864 | 865 | #[test] 866 | fn ctl_name() { 867 | let oid = vec![libc::CTL_KERN, libc::KERN_OSREV]; 868 | let name = super::oid2name(&oid).expect("Could not get name of kern.osrevision sysctl."); 869 | 870 | assert_eq!(name, "kern.osrevision"); 871 | 872 | let ctl = crate::Ctl::Oid(oid); 873 | let name = ctl 874 | .name() 875 | .expect("Could not get name of kern.osrevision sysctl."); 876 | assert_eq!(name, "kern.osrevision"); 877 | } 878 | 879 | #[test] 880 | fn ctl_type() { 881 | let oid = super::name2oid("kern").unwrap(); 882 | let fmt = super::oidfmt(&oid).unwrap(); 883 | assert_eq!(fmt.ctl_type, crate::CtlType::Node); 884 | let kern = crate::Ctl::new("kern").expect("Could not get kern node"); 885 | let value_type = kern.value_type().expect("Could not get kern value type"); 886 | assert_eq!(value_type, crate::CtlType::Node); 887 | 888 | let oid = super::name2oid("kern.osrelease").unwrap(); 889 | let fmt = super::oidfmt(&oid).unwrap(); 890 | assert_eq!(fmt.ctl_type, crate::CtlType::String); 891 | let osrelease = 892 | crate::Ctl::new("kern.osrelease").expect("Could not get kern.osrelease sysctl"); 893 | let value_type = osrelease 894 | .value_type() 895 | .expect("Could not get kern.osrelease value type"); 896 | assert_eq!(value_type, crate::CtlType::String); 897 | 898 | let oid = super::name2oid("kern.osrevision").unwrap(); 899 | let fmt = super::oidfmt(&oid).unwrap(); 900 | assert_eq!(fmt.ctl_type, crate::CtlType::Int); 901 | let osrevision = 902 | crate::Ctl::new("kern.osrevision").expect("Could not get kern.osrevision sysctl"); 903 | let value_type = osrevision 904 | .value_type() 905 | .expect("Could notget kern.osrevision value type"); 906 | assert_eq!(value_type, crate::CtlType::Int); 907 | } 908 | 909 | /// The name must be respresentable as a C String 910 | #[test] 911 | fn name2oid_invalid_name() { 912 | let r = super::name2oid("kern.\0.invalid.utf-8"); 913 | assert!(matches!(r, Err(super::SysctlError::NotFound(_)))); 914 | } 915 | } 916 | 917 | #[cfg(all(test, target_os = "freebsd"))] 918 | mod tests_freebsd { 919 | #[test] 920 | fn ctl_mib() { 921 | let oid = super::name2oid("kern.proc.pid").unwrap(); 922 | assert_eq!(oid.len(), 3); 923 | assert_eq!(oid[0], libc::CTL_KERN); 924 | assert_eq!(oid[1], libc::KERN_PROC); 925 | assert_eq!(oid[2], libc::KERN_PROC_PID); 926 | } 927 | } 928 | 929 | #[cfg(all(test, any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "visionos")))] 930 | mod tests_macos { 931 | #[test] 932 | fn ctl_mib() { 933 | let oid = super::name2oid("kern.proc.pid").unwrap(); 934 | assert_eq!(oid.len(), 3); 935 | assert_eq!(oid[0], libc::CTL_KERN); 936 | assert_eq!(oid[1], libc::KERN_PROC); 937 | assert_eq!(oid[2], libc::KERN_PROC_PID); 938 | } 939 | } 940 | -------------------------------------------------------------------------------- /src/unix/mod.rs: -------------------------------------------------------------------------------- 1 | // unix/mod.rs 2 | 3 | pub mod ctl; 4 | pub mod ctl_iter; 5 | pub mod funcs; 6 | --------------------------------------------------------------------------------