diff --git a/lib.rs b/lib.rs index 73f4d31..b87f9bd 100644 --- a/lib.rs +++ b/lib.rs @@ -281,6 +281,16 @@ pub struct DiskInfo { pub free: u64, } +#[cfg(target_family = "unix")] +mod uname; +#[cfg(target_family = "unix")] +pub use uname::Info as UnameInfo; + +#[cfg(not(target_family = "unix"))] +mod non_unix_uname; +#[cfg(not(target_family = "unix"))] +pub use non_unix_uname::Info as UnameInfo; + /// Error types #[derive(Debug)] pub enum Error { @@ -377,6 +387,11 @@ extern "C" { fn get_disk_info_bsd(di: &mut DiskInfo) -> i32; } +/// Returns the result of the unix `uname` syscall or, on non-unix systems, a similar +/// data-structure. +pub fn uname() -> Result { + UnameInfo::new() +} /// Get operation system type. /// @@ -445,19 +460,7 @@ pub fn os_release() -> Result { } #[cfg(any(target_os = "solaris", target_os = "illumos"))] { - let release: Option = unsafe { - let mut name: libc::utsname = std::mem::zeroed(); - if libc::uname(&mut name) < 0 { - None - } else { - let cstr = std::ffi::CStr::from_ptr(name.release.as_mut_ptr()); - Some(cstr.to_string_lossy().to_string()) - } - }; - match release { - None => Err(Error::Unknown), - Some(release) => Ok(release), - } + Ok(uname()?.release()?.to_string()) } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "solaris", target_os = "illumos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd")))] { @@ -934,4 +937,35 @@ mod test { let os_release = linux_os_release().unwrap(); println!("linux_os_release(): {:?}", os_release.name) } + + #[test] + pub fn test_uname() { + let uname = uname().unwrap(); + println!("uname: + sysname: {} + nodename: {} + release: {} + version: {}", + uname.sysname().unwrap(), + uname.nodename().unwrap(), + uname.release().unwrap(), + uname.version().unwrap(), + ); + assert!(uname.release().unwrap() == uname.release().unwrap()); + #[cfg(target_os = "Linux")] + assert!(uname.sysname().unwrap() == "Linux"); + #[cfg(target_family = "unix")] + println!(" machine: {}", + uname.machine().unwrap(), + ); + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "redox" + ))] + println!(" domainname: {}", + uname.domainname().unwrap(), + ); + } } diff --git a/non_unix_uname.rs b/non_unix_uname.rs new file mode 100644 index 0000000..0016ea6 --- /dev/null +++ b/non_unix_uname.rs @@ -0,0 +1,48 @@ +use super::Error; + +/// On non-unix systems, we emulate the uname data structure. +/// +/// Fields can be accessed through methods. +/// +/// Currently, this doesn't support `machine`. +pub struct Info { + sysname: String, + nodename: String, + release: String, +} + +impl Info { + pub(crate) fn new() -> Result { + let release = super::os_release()?; + Ok(Info { + sysname: super::os_type()?, + nodename: super::hostname()?, + release, + }) + } + + /// Kernel Name, for example "Linux". + pub fn sysname(&self) -> Result<&str, Error> { + Ok(&self.sysname) + } + /// Network node hostname, this is usually the same as the hostname. + pub fn nodename(&self) -> Result<&str, Error> { + Ok(&self.nodename) + } + /// Kernel release, for example "5.10.4-arch2-1". + pub fn release(&self) -> Result<&str, Error> { + Ok(&self.release) + } + /// Kernel version, for example "#1 SMP PREEMPT Fri, 01 Jan 2021 05:29:53 +0000". + /// + /// On non-unix systems, this is the same as `self.release()`. + pub fn version(&self) -> Result<&str, Error> { + Ok(&self.release) + } + /// Machine hardware name, for example "x86_64". + /// + /// Note that this isn't yet supported on non-unix systems. + pub fn machine(&self) -> Result<&str, Error> { + Err(Error::UnsupportedSystem) + } +} diff --git a/test/src/main.rs b/test/src/main.rs index e80cd27..41cb540 100644 --- a/test/src/main.rs +++ b/test/src/main.rs @@ -23,6 +23,32 @@ fn main() { let t = boottime().unwrap(); println!("boottime {} sec, {} usec", t.tv_sec, t.tv_usec); } + + let uname = sys_info::uname().unwrap(); + println!("uname: + sysname: {} + nodename: {} + release: {} + version: {}", + uname.sysname().unwrap(), + uname.nodename().unwrap(), + uname.release().unwrap(), + uname.version().unwrap(), + ); + #[cfg(target_family = "unix")] + println!(" machine: {}", + uname.machine().unwrap(), + ); + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "redox" + ))] + println!(" domainname: {}", + uname.domainname().unwrap(), + ); + #[cfg(target_os = "linux")] println!("/etc/os-release: {:?}", linux_os_release().unwrap()); } diff --git a/uname.rs b/uname.rs new file mode 100644 index 0000000..411b74e --- /dev/null +++ b/uname.rs @@ -0,0 +1,75 @@ +use std::ffi::CStr; +use std::io; + +use libc::{uname, utsname}; + +use super::Error; + +/// Result of `uname`, fields can be accessed through methods. +pub struct Info { + utsname: utsname, +} + +macro_rules! info_methods { + ( + $( + $(#[$meta:meta])* + $vis:vis fn $name:ident; + )* + ) => { + $( + $(#[$meta])* + $vis fn $name(&self) -> Result<&str, Error> { + let bytes = &self.utsname.$name; + // Make sure the string is null-terminated so we don't overflow. + if !bytes.iter().any(|b| *b == 0) { + return Err(Error::Unknown); + } + unsafe { CStr::from_ptr(bytes.as_ptr()) } + .to_str() + .map_err(|_| Error::Unknown) + } + )* + }; +} + +impl Info { + pub(crate) fn new() -> Result { + let mut info = Info { + utsname: unsafe { std::mem::zeroed() }, + }; + let ret = unsafe { uname(&mut info.utsname as *mut utsname) }; + if ret != 0 { + return Err(io::Error::last_os_error().into()); + } + Ok(info) + } + + info_methods!( + /// Kernel Name, for example "Linux". + pub fn sysname; + /// Network node hostname, this is usually the same as the hostname. + pub fn nodename; + /// Kernel release, for example "5.10.4-arch2-1". + pub fn release; + /// Kernel version, for example "#1 SMP PREEMPT Fri, 01 Jan 2021 05:29:53 +0000". + pub fn version; + /// Machine hardware name, for example "x86_64". + /// + /// Note that this isn't yet supported on non-unix systems. + pub fn machine; + ); + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "redox" + ))] + info_methods!( + /// Domain name of the system. + /// + /// This is only supported on linux, android, fuchsia and redox. + pub fn domainname; + ); +}