os_info-3.9.2/.cargo_vcs_info.json0000644000000001450000000000100125030ustar { "git": { "sha1": "983ee17a1457daac8561cd9c2f6ebba616689053" }, "path_in_vcs": "os_info" }os_info-3.9.2/Cargo.lock0000644000000114570000000000100104660ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "os_info" version = "3.9.2" dependencies = [ "doc-comment", "log", "pretty_assertions", "serde", "windows-sys", ] [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "serde" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" os_info-3.9.2/Cargo.toml0000644000000033770000000000100105130ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.60" name = "os_info" version = "3.9.2" authors = [ "Jan Schulte ", "Stanislav Tkach ", ] build = false include = [ "Cargo.toml", "LICENSE", "src/**/*.rs", "README.md", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Detect the operating system type and version." homepage = "https://github.com/stanislav-tkach/os_info" documentation = "https://docs.rs/os_info" readme = "README.md" keywords = [ "os", "os_type", "os_version", "os_info", ] categories = ["os"] license = "MIT" repository = "https://github.com/stanislav-tkach/os_info" [lib] name = "os_info" path = "src/lib.rs" [dependencies.log] version = "0.4" [dependencies.serde] version = "1" features = ["derive"] optional = true [dev-dependencies.doc-comment] version = "0.3" [dev-dependencies.pretty_assertions] version = "1" [features] default = ["serde"] [target."cfg(windows)".dependencies.windows-sys] version = "0.52" features = [ "Win32_Foundation", "Win32_System_LibraryLoader", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_UI_WindowsAndMessaging", ] os_info-3.9.2/Cargo.toml.orig000064400000000000000000000021241046102023000141610ustar 00000000000000[package] name = "os_info" version = "3.9.2" authors = ["Jan Schulte ", "Stanislav Tkach "] description = "Detect the operating system type and version." documentation = "https://docs.rs/os_info" homepage = "https://github.com/stanislav-tkach/os_info" repository = "https://github.com/stanislav-tkach/os_info" readme = "README.md" keywords = ["os", "os_type", "os_version", "os_info"] categories = ["os"] license = "MIT" edition = "2018" rust-version = "1.60" include = [ "Cargo.toml", "LICENSE", "src/**/*.rs", "README.md", ] [features] default = ["serde"] [dependencies] log.workspace = true serde = { version = "1", features = ["derive"], optional = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52", features = [ "Win32_Foundation", "Win32_System_LibraryLoader", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_UI_WindowsAndMessaging", ] } [dev-dependencies] pretty_assertions = "1" doc-comment = "0.3" os_info-3.9.2/LICENSE000064400000000000000000000020721046102023000123010ustar 00000000000000The MIT License (MIT) Copyright (c) 2017 Stanislav Tkach Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. os_info-3.9.2/README.md000064400000000000000000000064551046102023000125640ustar 00000000000000# os_info **Status:** [![CI](https://github.com/stanislav-tkach/os_info/workflows/CI/badge.svg)](https://github.com/stanislav-tkach/os_info/actions) [![Coverage](https://codecov.io/gh/stanislav-tkach/os_info/branch/master/graph/badge.svg)](https://codecov.io/gh/stanislav-tkach/os_info) [![Dependency status](https://deps.rs/repo/github/stanislav-tkach/os_info/status.svg)](https://deps.rs/repo/github/stanislav-tkach/os_info) **Project info:** [![Docs.rs](https://docs.rs/os_info/badge.svg)](https://docs.rs/os_info) [![Latest version](https://img.shields.io/crates/v/os_info.svg)](https://crates.io/crates/os_info) [![License](https://img.shields.io/github/license/stanislav-tkach/os_info.svg)](https://github.com/stanislav-tkach/os_info/blob/master/LICENSE) **Project details:** [![LoC](https://tokei.rs/b1/github/stanislav-tkach/os_info)](https://github.com/stanislav-tkach/os_info) ![Rust 1.60+ required](https://img.shields.io/badge/rust-1.41+-blue.svg?label=Required%20Rust) ## Overview This project consists of two parts: the library that can be used to detect the operating system type (including version and bitness) and the command line tool that uses the library. ### Library (`os_info`) #### `os_info` usage To use this crate, add `os_info` as a dependency to your project's Cargo.toml: ```toml [dependencies] os_info = "3" ``` This project has `serde` as an optional dependency, so if you don't need it, then you can speed up compilation disabling it: ```toml [dependencies] os_info = { version = "3", default-features = false } ``` #### Example ```rust let info = os_info::get(); // Print full information: println!("OS information: {info}"); // Print information separately: println!("Type: {}", info.os_type()); println!("Version: {}", info.version()); println!("Bitness: {}", info.bitness()); println!("Architecture: {}", info.architecture()); ``` ### Command line tool (`os_info_cli`) A simple wrapper around the `os_info` library. #### Installation This tool can be installed using the following cargo command: ```console cargo install os_info_cli ``` #### `os_info_cli` usage Despite being named `os_info_cli` during installation, it is actually named `os_info`. You can use the `--help` flag to see available options: ```console os_info --help ``` ## Supported operating systems Right now, the following operating system types can be returned: - AIX - AlmaLinux - Alpaquita Linux - Alpine Linux - Amazon Linux AMI - Android - Arch Linux - Artix Linux - CachyOS - CentOS - Debian - DragonFly BSD - Emscripten - EndeavourOS - Fedora - FreeBSD - Garuda Linux - Gentoo Linux - HardenedBSD - illumos - Kali Linux - Linux - Mabox - macOS (Mac OS X or OS X) - Manjaro - Mariner - MidnightBSD - Mint - NetBSD - NixOS - Nobara Linux - OpenBSD - OpenCloudOS - openEuler (EulerOS) - openSUSE - Oracle Linux - Pop!_OS - Raspberry Pi OS - Red Hat Linux - Red Hat Enterprise Linux - Redox - Rocky Linux - Solus - SUSE Linux Enterprise Server - Ubuntu - Ultramarine Linux - Unknown - Void Linux - Windows If you need support for more OS types, I am looking forward to your Pull Request. ## License `os_info` is licensed under the MIT license. See [LICENSE] for the details. [lsb_release]: http://refspecs.linuxbase.org/LSB_2.0.1/LSB-PDA/LSB-PDA/lsbrelease.html [LICENSE]: https://github.com/stanislav-tkach/os_info/blob/master/LICENSE os_info-3.9.2/src/aix/mod.rs000064400000000000000000000017151046102023000137740ustar 00000000000000use std::{process::Command, str}; use log::{error, trace}; use crate::{bitness, uname::uname, Info, Type, Version}; pub fn current_platform() -> Info { trace!("aix::current_platform is called"); let version = get_version() .map(Version::from_string) .unwrap_or_else(|| Version::Unknown); let info = Info { os_type: get_os(), version, bitness: bitness::get(), ..Default::default() }; trace!("Returning {:?}", info); info } fn get_version() -> Option { let major = uname("-v")?; let minor = uname("-r").unwrap_or(String::from("0")); Some(format!("{}.{}", major, minor)) } fn get_os() -> Type { match uname("-s").as_deref() { Some("AIX") => Type::AIX, _ => Type::Unknown, } } #[cfg(test)] mod tests { use super::*; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::AIX, version.os_type()); } } os_info-3.9.2/src/android/mod.rs000064400000000000000000000007041046102023000146300ustar 00000000000000use log::trace; use crate::{Bitness, Info, Type}; pub fn current_platform() -> Info { trace!("android::current_platform is called"); let info = Info::with_type(Type::Android); trace!("Returning {:?}", info); info } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::Android, version.os_type()); } } os_info-3.9.2/src/architecture.rs000064400000000000000000000013411046102023000151110ustar 00000000000000use std::process::Command; use log::error; pub fn get() -> Option { Command::new("uname") .arg("-m") .output() .map_err(|e| { error!("Cannot invoke 'uname` to get architecture type: {:?}", e); }) .ok() .and_then(|out| { if out.status.success() { Some(String::from_utf8_lossy(&out.stdout).trim_end().to_owned()) } else { log::error!("'uname' invocation error: {:?}", out); None } }) } #[cfg(test)] mod tests { use super::*; #[test] fn uname_nonempty() { let val = get().expect("architecture::get() failed"); assert!(!val.is_empty()); } } os_info-3.9.2/src/bitness.rs000064400000000000000000000077141046102023000141100ustar 00000000000000// spell-checker:ignore getconf use std::fmt::{self, Display, Formatter}; #[cfg(any( target_os = "aix", target_os = "dragonfly", target_os = "freebsd", target_os = "illumos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "openbsd" ))] use std::process::{Command, Output}; /// Operating system architecture in terms of how many bits compose the basic values it can deal with. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] pub enum Bitness { /// Unknown bitness (unable to determine). Unknown, /// 32-bit. X32, /// 64-bit. X64, } impl Display for Bitness { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match *self { Bitness::Unknown => write!(f, "unknown bitness"), Bitness::X32 => write!(f, "32-bit"), Bitness::X64 => write!(f, "64-bit"), } } } #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "linux", target_os = "macos", ))] pub fn get() -> Bitness { match &Command::new("getconf").arg("LONG_BIT").output() { Ok(Output { stdout, .. }) if stdout == b"32\n" => Bitness::X32, Ok(Output { stdout, .. }) if stdout == b"64\n" => Bitness::X64, _ => Bitness::Unknown, } } #[cfg(target_os = "netbsd")] pub fn get() -> Bitness { match &Command::new("sysctl") .arg("-n") .arg("hw.machine_arch") .output() { Ok(Output { stdout, .. }) if stdout == b"amd64\n" => Bitness::X64, Ok(Output { stdout, .. }) if stdout == b"x86_64\n" => Bitness::X64, Ok(Output { stdout, .. }) if stdout == b"i386\n" => Bitness::X32, Ok(Output { stdout, .. }) if stdout == b"aarch64\n" => Bitness::X64, Ok(Output { stdout, .. }) if stdout == b"earmv7hf\n" => Bitness::X32, Ok(Output { stdout, .. }) if stdout == b"sparc64\n" => Bitness::X64, _ => Bitness::Unknown, } } #[cfg(target_os = "openbsd")] pub fn get() -> Bitness { match &Command::new("sysctl").arg("-n").arg("hw.machine").output() { Ok(Output { stdout, .. }) if stdout == b"amd64\n" => Bitness::X64, Ok(Output { stdout, .. }) if stdout == b"x86_64\n" => Bitness::X64, Ok(Output { stdout, .. }) if stdout == b"i386\n" => Bitness::X32, Ok(Output { stdout, .. }) if stdout == b"aarch64\n" => Bitness::X64, Ok(Output { stdout, .. }) if stdout == b"earmv7hf\n" => Bitness::X32, Ok(Output { stdout, .. }) if stdout == b"sparc64\n" => Bitness::X64, _ => Bitness::Unknown, } } #[cfg(target_os = "illumos")] pub fn get() -> Bitness { match &Command::new("isainfo").arg("-b").output() { Ok(Output { stdout, .. }) if stdout == b"64\n" => Bitness::X64, Ok(Output { stdout, .. }) if stdout == b"32\n" => Bitness::X32, _ => Bitness::Unknown, } } #[cfg(target_os = "aix")] pub fn get() -> Bitness { match &Command::new("prtconf").arg("-c").output() { Ok(Output { stdout, .. }) if stdout == b"CPU Type: 64-bit\n" => Bitness::X64, Ok(Output { stdout, .. }) if stdout == b"CPU Type: 32-bit\n" => Bitness::X32, _ => Bitness::Unknown, } } #[cfg(all( test, any( target_os = "aix", target_os = "dragonfly", target_os = "freebsd", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "openbsd" ) ))] mod tests { use super::*; use pretty_assertions::assert_ne; #[test] fn get_bitness() { let b = get(); assert_ne!(b, Bitness::Unknown); } #[test] fn display() { let data = [ (Bitness::Unknown, "unknown bitness"), (Bitness::X32, "32-bit"), (Bitness::X64, "64-bit"), ]; for (bitness, expected) in &data { assert_eq!(&bitness.to_string(), expected); } } } os_info-3.9.2/src/dragonfly/mod.rs000064400000000000000000000013241046102023000151740ustar 00000000000000use std::process::Command; use log::trace; use crate::{bitness, uname::uname, Bitness, Info, Type, Version}; pub fn current_platform() -> Info { trace!("dragonfly::current_platform is called"); let version = uname("-r") .map(Version::from_string) .unwrap_or_else(|| Version::Unknown); let info = Info { os_type: Type::DragonFly, version, bitness: bitness::get(), ..Default::default() }; trace!("Returning {:?}", info); info } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::DragonFly, version.os_type()); } } os_info-3.9.2/src/emscripten/mod.rs000064400000000000000000000007671046102023000153720ustar 00000000000000use log::trace; use crate::{Bitness, Info, Type}; // TODO: Somehow get the real OS version? pub fn current_platform() -> Info { trace!("emscripten::current_platform is called"); let info = Info::with_type(Type::Emscripten); trace!("Returning {:?}", info); info } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::Emscripten, version.os_type()); } } os_info-3.9.2/src/freebsd/mod.rs000064400000000000000000000026731046102023000146310ustar 00000000000000use std::process::Command; use std::str; use log::{error, trace}; use crate::{bitness, uname::uname, Info, Type, Version}; pub fn current_platform() -> Info { trace!("freebsd::current_platform is called"); let version = uname("-r") .map(Version::from_string) .unwrap_or_else(|| Version::Unknown); let info = Info { os_type: get_os(), version, bitness: bitness::get(), ..Default::default() }; trace!("Returning {:?}", info); info } fn get_os() -> Type { match uname("-s").as_deref() { Some("MidnightBSD") => Type::MidnightBSD, Some("FreeBSD") => { let check_hardening = match Command::new("/sbin/sysctl") .arg("hardening.version") .output() { Ok(o) => o, Err(e) => { error!("Failed to invoke '/sbin/sysctl': {:?}", e); return Type::FreeBSD; } }; match str::from_utf8(&check_hardening.stderr) { Ok("0\n") => Type::HardenedBSD, Ok(_) => Type::FreeBSD, Err(_) => Type::FreeBSD, } } _ => Type::Unknown, } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::FreeBSD, version.os_type()); } } os_info-3.9.2/src/illumos/mod.rs000064400000000000000000000015371046102023000147010ustar 00000000000000use std::process::Command; use std::str; use log::{error, trace}; use crate::{bitness, uname::uname, Info, Type, Version}; pub fn current_platform() -> Info { trace!("illumos::current_platform is called"); let version = uname("-v") .map(Version::from_string) .unwrap_or_else(|| Version::Unknown); let info = Info { os_type: get_os(), version, bitness: bitness::get(), ..Default::default() }; trace!("Returning {:?}", info); info } fn get_os() -> Type { match uname("-o").as_deref() { Some("illumos") => Type::Illumos, _ => Type::Unknown, } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::Illumos, version.os_type()); } } os_info-3.9.2/src/info.rs000064400000000000000000000230431046102023000133650ustar 00000000000000// spell-checker:ignore itertools, iproduct, bitnesses use std::fmt::{self, Display, Formatter}; use super::{Bitness, Type, Version}; /// Holds information about operating system (type, version, etc.). /// /// The best way to get string representation of the operation system information is to use its /// `Display` implementation. /// /// # Examples /// /// ``` /// use os_info; /// /// let info = os_info::get(); /// println!("OS information: {info}"); /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Info { /// Operating system type. See `Type` for details. pub(crate) os_type: Type, /// Operating system version. See `Version` for details. pub(crate) version: Version, /// Operating system edition. pub(crate) edition: Option, /// Operating system codename. pub(crate) codename: Option, /// Operating system architecture in terms of how many bits compose the basic values it can deal /// with. See `Bitness` for details. pub(crate) bitness: Bitness, /// Processor architecture. pub(crate) architecture: Option, } impl Info { /// Constructs a new `Info` instance with unknown type, version and bitness. /// /// # Examples /// /// ``` /// use os_info::{Info, Type, Version, Bitness}; /// /// let info = Info::unknown(); /// assert_eq!(Type::Unknown, info.os_type()); /// assert_eq!(&Version::Unknown, info.version()); /// assert_eq!(None, info.edition()); /// assert_eq!(None, info.codename()); /// assert_eq!(Bitness::Unknown, info.bitness()); /// assert_eq!(None, info.architecture()); /// ``` pub fn unknown() -> Self { Self { os_type: Type::Unknown, version: Version::Unknown, edition: None, codename: None, bitness: Bitness::Unknown, architecture: None, } } /// Constructs a new `Info` instance with the specified operating system type. /// /// # Examples /// /// ``` /// use os_info::{Info, Type, Version, Bitness}; /// /// let os_type = Type::Linux; /// let info = Info::with_type(os_type); /// assert_eq!(os_type, info.os_type()); /// assert_eq!(&Version::Unknown, info.version()); /// assert_eq!(None, info.edition()); /// assert_eq!(None, info.codename()); /// assert_eq!(Bitness::Unknown, info.bitness()); /// assert_eq!(None, info.architecture()); /// ``` pub fn with_type(os_type: Type) -> Self { Self { os_type, ..Default::default() } } /// Returns operating system type. See `Type` for details. /// /// # Examples /// /// ``` /// use os_info::{Info, Type}; /// /// let info = Info::unknown(); /// assert_eq!(Type::Unknown, info.os_type()); /// ``` pub fn os_type(&self) -> Type { self.os_type } /// Returns operating system version. See `Version` for details. /// /// # Examples /// /// ``` /// use os_info::{Info, Version}; /// /// let info = Info::unknown(); /// assert_eq!(&Version::Unknown, info.version()); /// ``` pub fn version(&self) -> &Version { &self.version } /// Returns optional operation system edition. /// /// # Examples /// /// ``` /// use os_info::Info; /// /// let info = Info::unknown(); /// assert_eq!(None, info.edition()); pub fn edition(&self) -> Option<&str> { self.edition.as_ref().map(String::as_ref) } /// Returns optional operation system 'codename'. /// /// # Examples /// /// ``` /// use os_info::Info; /// /// let info = Info::unknown(); /// assert_eq!(None, info.codename()); pub fn codename(&self) -> Option<&str> { self.codename.as_ref().map(String::as_ref) } /// Returns operating system bitness. See `Bitness` for details. /// /// # Examples /// /// ``` /// use os_info::{Info, Bitness}; /// /// let info = Info::unknown(); /// assert_eq!(Bitness::Unknown, info.bitness()); /// ``` pub fn bitness(&self) -> Bitness { self.bitness } /// Returns operating system architecture. /// /// # Examples /// /// ``` /// use os_info::Info; /// /// let info = Info::unknown(); /// assert_eq!(None, info.architecture()); pub fn architecture(&self) -> Option<&str> { self.architecture.as_ref().map(String::as_ref) } } impl Default for Info { fn default() -> Self { Self::unknown() } } impl Display for Info { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", self.os_type)?; if self.version != Version::Unknown { write!(f, " {}", self.version)?; } if let Some(ref edition) = self.edition { write!(f, " ({edition})")?; } if let Some(ref codename) = self.codename { write!(f, " ({codename})")?; } write!(f, " [{}]", self.bitness) } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn unknown() { let info = Info::unknown(); assert_eq!(Type::Unknown, info.os_type()); assert_eq!(&Version::Unknown, info.version()); assert_eq!(None, info.edition()); assert_eq!(None, info.codename()); assert_eq!(Bitness::Unknown, info.bitness()); assert_eq!(None, info.architecture()); } #[test] fn with_type() { let types = [ Type::AIX, Type::Redox, Type::Alpaquita, Type::Alpine, Type::Amazon, Type::Android, Type::Arch, Type::Artix, Type::CachyOS, Type::CentOS, Type::Debian, Type::Emscripten, Type::EndeavourOS, Type::Fedora, Type::Gentoo, Type::Linux, Type::Macos, Type::Manjaro, Type::Mariner, Type::NixOS, Type::Nobara, Type::Uos, Type::OpenCloudOS, Type::openEuler, Type::openSUSE, Type::OracleLinux, Type::Pop, Type::Redhat, Type::RedHatEnterprise, Type::Redox, Type::Solus, Type::SUSE, Type::Ubuntu, Type::Ultramarine, Type::Void, Type::Mint, Type::Unknown, Type::Windows, ]; for t in &types { let info = Info::with_type(*t); assert_eq!(t, &info.os_type()); } } #[test] fn default() { assert_eq!(Info::default(), Info::unknown()); } #[test] fn display() { let data = [ // All unknown. (Info::unknown(), "Unknown [unknown bitness]"), // Type. ( Info { os_type: Type::Redox, ..Default::default() }, "Redox [unknown bitness]", ), // Type and version. ( Info { os_type: Type::Linux, version: Version::Semantic(2, 3, 4), ..Default::default() }, "Linux 2.3.4 [unknown bitness]", ), ( Info { os_type: Type::Arch, version: Version::Rolling(None), ..Default::default() }, "Arch Linux Rolling Release [unknown bitness]", ), ( Info { os_type: Type::Artix, version: Version::Rolling(None), ..Default::default() }, "Artix Linux Rolling Release [unknown bitness]", ), ( Info { os_type: Type::Manjaro, version: Version::Rolling(Some("2020.05.24".to_owned())), ..Default::default() }, "Manjaro Rolling Release (2020.05.24) [unknown bitness]", ), ( Info { os_type: Type::Windows, version: Version::Custom("Special Version".to_owned()), ..Default::default() }, "Windows Special Version [unknown bitness]", ), // Bitness. ( Info { bitness: Bitness::X32, ..Default::default() }, "Unknown [32-bit]", ), ( Info { bitness: Bitness::X64, ..Default::default() }, "Unknown [64-bit]", ), // All info. ( Info { os_type: Type::Macos, version: Version::Semantic(10, 2, 0), edition: Some("edition".to_owned()), codename: Some("codename".to_owned()), bitness: Bitness::X64, architecture: Some("architecture".to_owned()), }, "Mac OS 10.2.0 (edition) (codename) [64-bit]", ), ]; for (info, expected) in &data { assert_eq!(expected, &info.to_string()); } } } os_info-3.9.2/src/lib.rs000064400000000000000000000047501046102023000132040ustar 00000000000000//! `os_info` //! //! Provides interfaces for getting information about the current operating system, such as type, //! version, edition and bitness. #![deny( missing_debug_implementations, missing_docs, unsafe_code, missing_doc_code_examples )] #[cfg(target_os = "aix")] #[path = "aix/mod.rs"] mod imp; #[cfg(target_os = "android")] #[path = "android/mod.rs"] mod imp; #[cfg(target_os = "dragonfly")] #[path = "dragonfly/mod.rs"] mod imp; #[cfg(target_os = "emscripten")] #[path = "emscripten/mod.rs"] mod imp; #[cfg(target_os = "freebsd")] #[path = "freebsd/mod.rs"] mod imp; #[cfg(target_os = "illumos")] #[path = "illumos/mod.rs"] mod imp; #[cfg(target_os = "linux")] #[path = "linux/mod.rs"] mod imp; #[cfg(target_os = "macos")] #[path = "macos/mod.rs"] mod imp; #[cfg(target_os = "netbsd")] #[path = "netbsd/mod.rs"] mod imp; #[cfg(target_os = "openbsd")] #[path = "openbsd/mod.rs"] mod imp; #[cfg(target_os = "redox")] #[path = "redox/mod.rs"] mod imp; #[cfg(windows)] #[path = "windows/mod.rs"] mod imp; #[cfg(not(any( target_os = "aix", target_os = "android", target_os = "dragonfly", target_os = "emscripten", target_os = "freebsd", target_os = "illumos", target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "windows" )))] #[path = "unknown/mod.rs"] mod imp; #[cfg(any( target_os = "linux", target_os = "macos", target_os = "netbsd", target_os = "openbsd" ))] mod architecture; mod bitness; mod info; #[cfg(not(windows))] mod matcher; mod os_type; #[cfg(any( target_os = "aix", target_os = "dragonfly", target_os = "freebsd", target_os = "illumos", target_os = "netbsd", target_os = "openbsd" ))] mod uname; mod version; pub use crate::{bitness::Bitness, info::Info, os_type::Type, version::Version}; /// Returns information about the current operating system (type, version, edition, etc.). /// /// # Examples /// /// ``` /// use os_info; /// /// let info = os_info::get(); /// /// // Print full information: /// println!("OS information: {info}"); /// /// // Print information separately: /// println!("Type: {}", info.os_type()); /// println!("Version: {}", info.version()); /// println!("Edition: {:?}", info.edition()); /// println!("Codename: {:?}", info.codename()); /// println!("Bitness: {}", info.bitness()); /// println!("Architecture: {:?}", info.architecture()); /// ``` pub fn get() -> Info { imp::current_platform() } os_info-3.9.2/src/linux/file_release.rs000064400000000000000000000534601046102023000162160ustar 00000000000000// spell-checker:ignore sles use std::{fmt, fs::File, io::Read, path::Path}; use log::{trace, warn}; use crate::{matcher::Matcher, Bitness, Info, Type, Version}; pub fn get() -> Option { retrieve(&DISTRIBUTIONS, "/") } fn retrieve(distributions: &[ReleaseInfo], root: &str) -> Option { for release_info in distributions { let path = Path::new(root).join(release_info.path); if !path.exists() { trace!("Path '{}' doesn't exist", release_info.path); continue; } let mut file = match File::open(&path) { Ok(val) => val, Err(e) => { warn!("Unable to open {:?} file: {:?}", &path, e); continue; } }; let mut file_content = String::new(); if let Err(e) = file.read_to_string(&mut file_content) { warn!("Unable to read {:?} file: {:?}", &path, e); continue; } let os_type = (release_info.os_type)(&file_content); // If os_type is indeterminate, try the next release_info if os_type.is_none() { continue; } let version = (release_info.version)(&file_content); return Some(Info { // Unwrap is OK here because of the `os_type.is_none()` check above. os_type: os_type.unwrap(), version: version.unwrap_or(Version::Unknown), bitness: Bitness::Unknown, ..Default::default() }); } // Failed to determine os info None } /// Struct containing information on how to parse distribution info from a release file. #[derive(Clone)] struct ReleaseInfo<'a> { /// Relative path to the release file this struct corresponds to from root. path: &'a str, /// A closure that determines the os type from the release file contents. os_type: for<'b> fn(&'b str) -> Option, /// A closure that determines the os version from the release file contents. version: for<'b> fn(&'b str) -> Option, } impl fmt::Debug for ReleaseInfo<'_> { fn fmt<'a>(&'a self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ReleaseInfo") .field("path", &self.path) .field("os_type", &(self.os_type as fn(&'a str) -> Option)) .field("version", &(self.version as fn(&'a str) -> Option)) .finish() } } /// List of all supported distributions and the information on how to parse their version from the /// release file. static DISTRIBUTIONS: [ReleaseInfo; 6] = [ // Keep this first; most modern distributions have this file. ReleaseInfo { path: "etc/os-release", os_type: |release| { Matcher::KeyValue { key: "ID" } .find(release) .and_then(|id| match id.as_str() { // os-release information collected from // https://github.com/chef/os_release "almalinux" => Some(Type::AlmaLinux), "alpaquita" => Some(Type::Alpaquita), "alpine" => Some(Type::Alpine), "amzn" => Some(Type::Amazon), //"antergos" => Antergos //"aosc" => AOSC "arch" => Some(Type::Arch), "archarm" => Some(Type::Arch), "artix" => Some(Type::Artix), "cachyos" => Some(Type::CachyOS), "centos" => Some(Type::CentOS), //"clear-linux-os" => ClearLinuxOS //"clearos" => ClearOS //"coreos" //"cumulus-linux" => Cumulus "debian" => Some(Type::Debian), //"devuan" => Devuan //"elementary" => Elementary "fedora" => Some(Type::Fedora), //"gentoo" => Gentoo //"ios_xr" => ios_xr "kali" => Some(Type::Kali), //"mageia" => Mageia //"manjaro" => Manjaro "linuxmint" => Some(Type::Mint), "mariner" => Some(Type::Mariner), //"nexus" => Nexus "nixos" => Some(Type::NixOS), "nobara" => Some(Type::Nobara), "Uos" => Some(Type::Uos), "opencloudos" => Some(Type::OpenCloudOS), "openEuler" => Some(Type::openEuler), "ol" => Some(Type::OracleLinux), "opensuse" => Some(Type::openSUSE), "opensuse-leap" => Some(Type::openSUSE), "opensuse-microos" => Some(Type::openSUSE), "opensuse-tumbleweed" => Some(Type::openSUSE), //"rancheros" => RancherOS //"raspbian" => Raspbian // note XBian also uses "raspbian" "rhel" => Some(Type::RedHatEnterprise), "rocky" => Some(Type::RockyLinux), //"sabayon" => Sabayon //"scientific" => Scientific //"slackware" => Slackware "sled" => Some(Type::SUSE), // SUSE desktop "sles" => Some(Type::SUSE), "sles_sap" => Some(Type::SUSE), // SUSE SAP "ubuntu" => Some(Type::Ubuntu), "ultramarine" => Some(Type::Ultramarine), //"virtuozzo" => Virtuozzo "void" => Some(Type::Void), //"XCP-ng" => xcp-ng //"xenenterprise" => xcp-ng //"xenserver" => xcp-ng _ => None, }) }, version: |release| { Matcher::KeyValue { key: "VERSION_ID" } .find(release) .map(Version::from_string) }, }, // Older distributions must have their specific release file parsed. ReleaseInfo { path: "etc/mariner-release", os_type: |_| Some(Type::Mariner), version: |release| { Matcher::PrefixedVersion { prefix: "CBL-Mariner", } .find(release) .map(Version::from_string) }, }, ReleaseInfo { path: "etc/centos-release", os_type: |_| Some(Type::CentOS), version: |release| { Matcher::PrefixedVersion { prefix: "release" } .find(release) .map(Version::from_string) }, }, ReleaseInfo { path: "etc/fedora-release", os_type: |_| Some(Type::Fedora), version: |release| { Matcher::PrefixedVersion { prefix: "release" } .find(release) .map(Version::from_string) }, }, ReleaseInfo { path: "etc/alpine-release", os_type: |_| Some(Type::Alpine), version: |release| Matcher::AllTrimmed.find(release).map(Version::from_string), }, ReleaseInfo { path: "etc/redhat-release", os_type: |_| Some(Type::RedHatEnterprise), version: |release| { Matcher::PrefixedVersion { prefix: "release" } .find(release) .map(Version::from_string) }, }, ]; #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn almalinux_9_0_release() { let root = "src/linux/tests/AlmaLinux-9.0"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::AlmaLinux); assert_eq!(info.version, Version::Semantic(9, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn alpaquita_os_release() { let root = "src/linux/tests/Alpaquita"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Alpaquita); assert_eq!(info.version, Version::Semantic(23, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn alpine_3_12_os_release() { let root = "src/linux/tests/Alpine_3_12"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Alpine); assert_eq!(info.version, Version::Semantic(3, 12, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn alpine_release() { let root = "src/linux/tests/Alpine"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Alpine); assert_eq!(info.version, Version::Custom("A.B.C".to_owned())); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn amazon_1_os_release() { let root = "src/linux/tests/Amazon_1"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Amazon); assert_eq!(info.version, Version::Semantic(2018, 3, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn amazon_2_os_release() { let root = "src/linux/tests/Amazon_2"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Amazon); assert_eq!(info.version, Version::Semantic(2, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn arch_os_release() { let root = "src/linux/tests/Arch"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Arch); assert_eq!(info.version, Version::Unknown); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn archarm_os_release() { let root = "src/linux/tests/ArchARM"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Arch); assert_eq!(info.version, Version::Unknown); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn artix_os_release() { let root = "src/linux/tests/Artix"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Artix); assert_eq!(info.version, Version::Unknown); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn centos_7_os_release() { let root = "src/linux/tests/CentOS_7"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::CentOS); assert_eq!(info.version, Version::Semantic(7, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn centos_stream_os_release() { let root = "src/linux/tests/CentOS_Stream"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::CentOS); assert_eq!(info.version, Version::Semantic(8, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn centos_release() { let root = "src/linux/tests/CentOS"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::CentOS); assert_eq!(info.version, Version::Custom("XX".to_owned())); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn centos_release_unknown() { let root = "src/linux/tests/CentOS_Unknown"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::CentOS); assert_eq!(info.version, Version::Unknown); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn debian_11_os_release() { let root = "src/linux/tests/Debian_11"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Debian); assert_eq!(info.version, Version::Semantic(11, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn fedora_32_os_release() { let root = "src/linux/tests/Fedora_32"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Fedora); assert_eq!(info.version, Version::Semantic(32, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn fedora_35_os_release() { let root = "src/linux/tests/Fedora_35"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Fedora); assert_eq!(info.version, Version::Semantic(35, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn fedora_release() { let root = "src/linux/tests/Fedora"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Fedora); assert_eq!(info.version, Version::Semantic(26, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn fedora_release_unknown() { let root = "src/linux/tests/Fedora_Unknown"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Fedora); assert_eq!(info.version, Version::Unknown); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn kali_2023_2_os_release() { let root = "src/linux/tests/Kali_2023_2"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Kali); assert_eq!(info.version, Version::Semantic(2023, 2, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn mariner_release() { let root = "src/linux/tests/Mariner"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Mariner); assert_eq!(info.version, Version::Semantic(2, 0, 20220210)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn mariner_release_unknown() { let root = "src/linux/tests/Mariner_Unknown"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Mariner); assert_eq!(info.version, Version::Unknown); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn mint_os_release() { let root = "src/linux/tests/Mint"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Mint); assert_eq!(info.version, Version::Semantic(20, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn nixos_os_release() { let root = "src/linux/tests/NixOS"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::NixOS); assert_eq!( info.version, Version::Custom("21.05pre275822.916ee862e87".to_string()) ); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn nobara_os_release() { let root = "src/linux/tests/Nobara"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Nobara); assert_eq!(info.version, Version::Semantic(39, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn uos_os_release() { let root = "src/linux/tests/Uos"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Uos); assert_eq!(info.version, Version::Semantic(20, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn none_invalid_os_release() { let root = "src/linux/tests/none_invalid_os_release"; let info = retrieve(&DISTRIBUTIONS, root); assert_eq!(info, None); } #[test] fn none_no_release() { let root = "src/linux/tests/none_no_release"; let info = retrieve(&DISTRIBUTIONS, root); assert_eq!(info, None); } #[test] fn none_no_path() { let root = "src/linux/tests/none_no_path"; let info = retrieve(&DISTRIBUTIONS, root); assert_eq!(info, None); } #[test] fn opencloudos_os_release() { let root = "src/linux/tests/OpenCloudOS"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::OpenCloudOS); assert_eq!(info.version, Version::Semantic(8, 6, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn openeuler_os_release() { let root = "src/linux/tests/openEuler"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::openEuler); assert_eq!(info.version, Version::Semantic(22, 3, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn opensuse_tumbleweed_os_release() { let root = "src/linux/tests/openSUSE_Tumbleweed"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::openSUSE); assert_eq!(info.version, Version::Semantic(20230816, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn oracle_linux_os_release() { let root = "src/linux/tests/OracleLinux"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::OracleLinux); assert_eq!(info.version, Version::Semantic(8, 1, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn rhel_8_os_release() { let root = "src/linux/tests/RedHatEnterprise_8"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::RedHatEnterprise); assert_eq!(info.version, Version::Semantic(8, 2, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn rhel_7_os_release() { let root = "src/linux/tests/RedHatEnterprise_7"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::RedHatEnterprise); assert_eq!(info.version, Version::Semantic(7, 9, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn redhat_release() { let root = "src/linux/tests/RedHatEnterprise"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::RedHatEnterprise); assert_eq!(info.version, Version::Custom("XX".to_owned())); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn redhat_release_unknown() { let root = "src/linux/tests/RedHatEnterprise_Unknown"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::RedHatEnterprise); assert_eq!(info.version, Version::Unknown); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn rocky_9_2_release() { let root = "src/linux/tests/RockyLinux-9.2"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::RockyLinux); assert_eq!(info.version, Version::Semantic(9, 2, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn suse_12_os_release() { let root = "src/linux/tests/SUSE_12"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::SUSE); assert_eq!(info.version, Version::Semantic(12, 5, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn suse_15_os_release() { let root = "src/linux/tests/SUSE_15"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::SUSE); assert_eq!(info.version, Version::Semantic(15, 2, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn ubuntu_os_release() { let root = "src/linux/tests/Ubuntu"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Ubuntu); assert_eq!(info.version, Version::Semantic(18, 10, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn ultramarine_os_release() { let root = "src/linux/tests/Ultramarine"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Ultramarine); assert_eq!(info.version, Version::Semantic(39, 0, 0)); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn void_os_release() { let root = "src/linux/tests/Void"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::Void); assert_eq!(info.version, Version::Unknown); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn cachy_os_release() { let root = "src/linux/tests/CachyOS"; let info = retrieve(&DISTRIBUTIONS, root).unwrap(); assert_eq!(info.os_type(), Type::CachyOS); assert_eq!(info.version, Version::Unknown); assert_eq!(info.edition, None); assert_eq!(info.codename, None); } #[test] fn release_info_debug() { dbg!("{:?}", &DISTRIBUTIONS[0]); } } os_info-3.9.2/src/linux/lsb_release.rs000064400000000000000000000466501046102023000160620ustar 00000000000000// spell-checker:ignore codename, noarch, rhel, ootpa, maipo use std::process::Command; use log::{debug, trace}; use crate::{matcher::Matcher, Info, Type, Version}; pub fn get() -> Option { let release = retrieve()?; let version = match release.version.as_deref() { Some("rolling") => Version::Rolling(None), Some(v) => Version::from_string(v.to_owned()), None => Version::Unknown, }; let os_type = match release.distribution.as_ref().map(String::as_ref) { Some("Alpaquita") => Type::Alpaquita, Some("Amazon") | Some("AmazonAMI") => Type::Amazon, Some("Arch") => Type::Arch, Some("Artix") => Type::Artix, Some("cachyos") => Type::CachyOS, Some("CentOS") => Type::CentOS, Some("Debian") => Type::Debian, Some("EndeavourOS") => Type::EndeavourOS, Some("Fedora") | Some("Fedora Linux") => Type::Fedora, Some("Garuda") => Type::Garuda, Some("Gentoo") => Type::Gentoo, Some("Kali") => Type::Kali, Some("Linuxmint") => Type::Mint, Some("MaboxLinux") => Type::Mabox, Some("ManjaroLinux") => Type::Manjaro, Some("Mariner") => Type::Mariner, Some("NixOS") => Type::NixOS, Some("NobaraLinux") => Type::Nobara, Some("Uos") => Type::Uos, Some("OpenCloudOS") => Type::OpenCloudOS, Some("openEuler") => Type::openEuler, Some("openSUSE") => Type::openSUSE, Some("OracleServer") => Type::OracleLinux, Some("Pop") => Type::Pop, Some("Raspbian") => Type::Raspbian, Some("RedHatEnterprise") | Some("RedHatEnterpriseServer") => Type::RedHatEnterprise, Some("Solus") => Type::Solus, Some("SUSE") => Type::SUSE, Some("Ubuntu") => Type::Ubuntu, Some("UltramarineLinux") => Type::Ultramarine, Some("VoidLinux") => Type::Void, _ => Type::Linux, }; Some(Info { os_type, version, codename: release.codename, ..Default::default() }) } struct LsbRelease { pub distribution: Option, pub version: Option, pub codename: Option, } fn retrieve() -> Option { match Command::new("lsb_release").arg("-a").output() { Ok(output) => { trace!("lsb_release command returned {:?}", output); Some(parse(&String::from_utf8_lossy(&output.stdout))) } Err(e) => { debug!("lsb_release command failed with {:?}", e); None } } } fn parse(output: &str) -> LsbRelease { trace!("Trying to parse {:?}", output); let distribution = Matcher::PrefixedWord { prefix: "Distributor ID:", } .find(output); let codename = Matcher::PrefixedWord { prefix: "Codename:", } .find(output) .filter(|c| c != "n/a"); let version = Matcher::PrefixedVersion { prefix: "Release:" }.find(output); trace!( "Parsed as '{:?}' distribution and '{:?}' version", distribution, version ); LsbRelease { distribution, version, codename, } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn debian() { let parse_results = parse(file()); assert_eq!(parse_results.distribution, Some("Debian".to_string())); assert_eq!(parse_results.version, Some("7.8".to_string())); assert_eq!(parse_results.codename, Some("wheezy".to_string())); } #[test] fn alpaquita() { let parse_results = parse(alpaquita_file()); assert_eq!(parse_results.distribution, Some("Alpaquita".to_string())); assert_eq!(parse_results.version, Some("23".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn arch() { let parse_results = parse(arch_file()); assert_eq!(parse_results.distribution, Some("Arch".to_string())); assert_eq!(parse_results.version, Some("rolling".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn artix() { let parse_results = parse(artix_file()); assert_eq!(parse_results.distribution, Some("Artix".to_string())); assert_eq!(parse_results.version, Some("rolling".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn fedora() { let parse_results = parse(fedora_file()); assert_eq!(parse_results.distribution, Some("Fedora".to_string())); assert_eq!(parse_results.version, Some("26".to_string())); assert_eq!(parse_results.codename, Some("TwentySix".to_string())); } #[test] fn kali_2023_2() { let parse_results = parse(kali_2023_2_file()); assert_eq!(parse_results.distribution, Some("Kali".to_string())); assert_eq!(parse_results.version, Some("2023.2".to_string())); assert_eq!(parse_results.codename, Some("kali-rolling".to_string())); } #[test] fn ubuntu() { let parse_results = parse(ubuntu_file()); assert_eq!(parse_results.distribution, Some("Ubuntu".to_string())); assert_eq!(parse_results.version, Some("16.04".to_string())); assert_eq!(parse_results.codename, Some("xenial".to_string())); } #[test] fn mint() { let parse_results = parse(mint_file()); assert_eq!(parse_results.distribution, Some("Linuxmint".to_string())); assert_eq!(parse_results.version, Some("20".to_string())); assert_eq!(parse_results.codename, Some("ulyana".to_string())); } #[test] fn nixos() { let parse_results = parse(nixos_file()); assert_eq!(parse_results.distribution, Some("NixOS".to_string())); assert_eq!( parse_results.version, Some("21.05pre275822.916ee862e87".to_string()) ); assert_eq!(parse_results.codename, Some("okapi".to_string())); } #[test] fn nobara() { let parse_results = parse(nobara_file()); assert_eq!(parse_results.distribution, Some("NobaraLinux".to_string())); assert_eq!(parse_results.version, Some("39".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn uos() { let parse_results = parse(uos_file()); assert_eq!(parse_results.distribution, Some("uos".to_string())); assert_eq!(parse_results.version, Some("20".to_string())); assert_eq!(parse_results.codename, Some("eagle".to_string())); } #[test] fn amazon1() { let parse_results = parse(amazon1_file()); assert_eq!(parse_results.distribution, Some("AmazonAMI".to_string())); assert_eq!(parse_results.version, Some("2018.03".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn amazon2() { let parse_results = parse(amazon2_file()); assert_eq!(parse_results.distribution, Some("Amazon".to_string())); assert_eq!(parse_results.version, Some("2".to_string())); assert_eq!(parse_results.codename, Some("Karoo".to_string())); } #[test] fn redhat_enterprise_8() { let parse_results = parse(rhel8_file()); assert_eq!( parse_results.distribution, Some("RedHatEnterprise".to_string()) ); assert_eq!(parse_results.version, Some("8.1".to_string())); assert_eq!(parse_results.codename, Some("Ootpa".to_string())); } #[test] fn redhat_enterprise_7() { let parse_results = parse(rhel7_file()); assert_eq!( parse_results.distribution, Some("RedHatEnterpriseServer".to_string()) ); assert_eq!(parse_results.version, Some("7.7".to_string())); assert_eq!(parse_results.codename, Some("Maipo".to_string())); } #[test] fn redhat_enterprise_6() { let parse_results = parse(rhel6_file()); assert_eq!( parse_results.distribution, Some("RedHatEnterpriseServer".to_string()) ); assert_eq!(parse_results.version, Some("6.10".to_string())); assert_eq!(parse_results.codename, Some("Santiago".to_string())); } #[test] fn suse_enterprise_15_1() { let parse_results = parse(suse_enterprise15_1_file()); assert_eq!(parse_results.distribution, Some("SUSE".to_string())); assert_eq!(parse_results.version, Some("15.1".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn suse_enterprise_12_5() { let parse_results = parse(suse_enterprise12_5_file()); assert_eq!(parse_results.distribution, Some("SUSE".to_string())); assert_eq!(parse_results.version, Some("12.5".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn open_suse_15_1() { let parse_results = parse(open_suse_15_1_file()); assert_eq!(parse_results.distribution, Some("openSUSE".to_string())); assert_eq!(parse_results.version, Some("15.1".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn oracle_linux_7_5() { let parse_results = parse(oracle_server_linux_7_5_file()); assert_eq!(parse_results.distribution, Some("OracleServer".to_string())); assert_eq!(parse_results.version, Some("7.5".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn oracle_linux_8_1() { let parse_results = parse(oracle_server_linux_8_1_file()); assert_eq!(parse_results.distribution, Some("OracleServer".to_string())); assert_eq!(parse_results.version, Some("8.1".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn pop_os_20_04_lts() { let parse_results = parse(pop_os_20_04_lts_file()); assert_eq!(parse_results.distribution, Some("Pop".to_string())); assert_eq!(parse_results.version, Some("20.04".to_string())); assert_eq!(parse_results.codename, Some("focal".to_string())); } #[test] fn solus_4_1() { let parse_results = parse(solus_4_1_file()); assert_eq!(parse_results.distribution, Some("Solus".to_string())); assert_eq!(parse_results.version, Some("4.1".to_string())); assert_eq!(parse_results.codename, Some("fortitude".to_string())); } #[test] fn manjaro() { let parse_results = parse(manjaro_19_0_2_file()); assert_eq!(parse_results.distribution, Some("ManjaroLinux".to_string())); assert_eq!(parse_results.version, Some("19.0.2".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn mariner() { let parse_results = parse(mariner_file()); assert_eq!(parse_results.distribution, Some("Mariner".to_string())); assert_eq!(parse_results.version, Some("2.0.20220210".to_string())); assert_eq!(parse_results.codename, Some("Mariner".to_string())); } #[test] fn endeavouros() { let parse_results = parse(endeavouros_file()); assert_eq!(parse_results.distribution, Some("EndeavourOS".to_string())); assert_eq!(parse_results.version, Some("rolling".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn ultramarine() { let parse_results = parse(ultramarine_file()); assert_eq!( parse_results.distribution, Some("UltramarineLinux".to_string()) ); assert_eq!(parse_results.version, Some("39".to_string())); assert_eq!(parse_results.codename, Some("kuma".to_string())); } #[test] fn void_linux() { let parse_results = parse(void_file()); assert_eq!(parse_results.distribution, Some("Void".to_string())); assert_eq!(parse_results.version, Some("rolling".to_string())); } #[test] fn raspbian() { let parse_results = parse(raspberry_os_file()); assert_eq!(parse_results.distribution, Some("Raspbian".to_string())); assert_eq!(parse_results.version, Some("10".to_string())); assert_eq!(parse_results.codename, None); } #[test] fn cachyos() { let parse_results = parse(cachyos_file()); assert_eq!(parse_results.distribution, Some("cachyos".to_string())); assert_eq!(parse_results.version, Some("rolling".to_string())); } fn file() -> &'static str { "\nDistributor ID: Debian\n\ Description: Debian GNU/Linux 7.8 (wheezy)\n\ Release: 7.8\n\ Codename: wheezy\n\ " } fn alpaquita_file() -> &'static str { "\nDistributor ID: Alpaquita\n\ Description: BellSoft Alpaquita Linux Stream 23 (musl)\n\ Release: 23\n\ Codename: n/a" } fn arch_file() -> &'static str { "\nLSB Version: 1.4\n\ Distributor ID: Arch\n\ Description: Arch Linux\n\ Release: rolling\n\ Codename: n/a" } fn artix_file() -> &'static str { "\nLSB Version: n/a\n\ Distributor ID: Artix\n\ Description: Artix Linux\n\ Release: rolling\n\ Codename: n/a" } fn fedora_file() -> &'static str { "\nLSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch\n\ Distributor ID: Fedora\n\ Description: Fedora release 26 (Twenty Six)\n\ Release: 26\n\ Codename: TwentySix\n\ " } fn kali_2023_2_file() -> &'static str { "\nDistributor ID: Kali\n\ Description: Kali GNU/Linux Rolling\n\ Release: 2023.2\n\ Codename: kali-rolling\n\ " } fn ubuntu_file() -> &'static str { "Distributor ID: Ubuntu\n\ Description: Ubuntu 16.04.5 LTS\n\ Release: 16.04\n\ Codename: xenial" } fn mint_file() -> &'static str { "Distributor ID: Linuxmint\n\ Description: Linux Mint 20\n\ Release: 20\n\ Codename: ulyana" } fn nixos_file() -> &'static str { "Distributor ID: NixOS\n\ Description: NixOS 21.05 (Okapi)\n\ Release: 21.05pre275822.916ee862e87\n\ Codename: okapi" } fn nobara_file() -> &'static str { "LSB Version: n/a\n\ Distributor ID: NobaraLinux\n\ Description: Nobara Linux 39 (KDE Plasma)\n\ Release: 39\n\ Codename: n/a\n\ " } fn uos_file() -> &'static str { "Distributor ID: uos\n\ Description: UnionTech OS 20\n\ Release: 20\n\ Codename: eagle\n\ " } // Amazon Linux 1 uses a separate Distributor ID and Release format from Amazon Linux 2 fn amazon1_file() -> &'static str { "LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch\n\ Distributor ID: AmazonAMI\n\ Description: Amazon Linux AMI release 2018.03\n\ Release: 2018.03\n\ Codename: n/a\n\ " } // Amazon Linux 2 uses a separate Distributor ID and Release format from Amazon Linux 1 fn amazon2_file() -> &'static str { "LSB Version: :core-4.1-amd64:core-4.1-noarch\n\ Distributor ID: Amazon\n\ Description: Amazon Linux release 2 (Karoo)\n\ Release: 2\n\ Codename: Karoo\n\ " } fn rhel8_file() -> &'static str { "LSB Version: :core-4.1-amd64:core-4.1-noarch\n\ Distributor ID: RedHatEnterprise\n\ Description: Red Hat Enterprise Linux release 8.1 (Ootpa)\n\ Release: 8.1\n\ Codename: Ootpa\n\ " } fn rhel7_file() -> &'static str { "LSB Version: :core-4.1-amd64:core-4.1-noarch\n\ Distributor ID: RedHatEnterpriseServer\n\ Description: Red Hat Enterprise Linux Server release 7.7 (Maipo)\n\ Release: 7.7\n\ Codename: Maipo\n\ " } fn rhel6_file() -> &'static str { "LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch\n\ Distributor ID: RedHatEnterpriseServer\n\ Description: Red Hat Enterprise Linux Server release 6.10 (Santiago)\n\ Release: 6.10\n\ Codename: Santiago\n\ " } fn suse_enterprise15_1_file() -> &'static str { "LSB Version: n/a\n\ Distributor ID: SUSE\n\ Description: SUSE Linux Enterprise Server 15 SP1\n\ Release: 15.1\n\ Codename: n/a\n\ " } fn suse_enterprise12_5_file() -> &'static str { "LSB Version: n/a\n\ Distributor ID: SUSE\n\ Description: SUSE Linux Enterprise Server 12 SP5\n\ Release: 12.5\n\ Codename: n/a\n\ " } fn raspberry_os_file() -> &'static str { "LSB Version: n/a\n\ Distributor ID: Raspbian\n\ Description: Raspbian GNU/Linux 10 (buster)\n\ Release: 10\n\ Codename: n/a\n\ " } fn open_suse_15_1_file() -> &'static str { "LSB Version: n/a\n\ Distributor ID: openSUSE\n\ Description: openSUSE Leap 15.1\n\ Release: 15.1\n\ Codename: n/a\n\ " } fn oracle_server_linux_7_5_file() -> &'static str { "LSB Version: :core-4.1-amd64:core-4.1-noarch\n\ Distributor ID: OracleServer\n\ Description: Oracle Linux Server release 7.5\n\ Release: 7.5\n\ Codename: n/a\n\ " } fn oracle_server_linux_8_1_file() -> &'static str { "LSB Version: :core-4.1-amd64:core-4.1-noarch\n\ Distributor ID: OracleServer\n\ Description: Oracle Linux Server release 8.1\n\ Release: 8.1\n\ Codename: n/a\n\ " } fn pop_os_20_04_lts_file() -> &'static str { "No LSB modules are available.\n\ Distributor ID: Pop\n\ Description: Pop!_OS 20.04 LTS\n\ Release: 20.04\n\ Codename: focal\n\ " } fn solus_4_1_file() -> &'static str { "LSB Version: 1.4\n\ Distributor ID: Solus\n\ Description: Solus\n\ Release: 4.1\n\ Codename: fortitude\n\ " } fn manjaro_19_0_2_file() -> &'static str { "LSB Version: n/a\n\ Distributor ID: ManjaroLinux\n\ Description: Manjaro Linux\n\ Release: 19.0.2\n\ Codename: n/a\n\ " } fn mariner_file() -> &'static str { "LSB Version: n/a\n\ Distributor ID: Mariner\n\ Description: CBL-Mariner 2.0.20220210\n\ Release: 2.0.20220210\n\ Codename: Mariner\n\ " } fn endeavouros_file() -> &'static str { "LSB Version: 1.4\n\ Distributor ID: EndeavourOS\n\ Description: EndeavourOS Linux\n\ Release: rolling\n\ Codename: n/a\n\ " } fn ultramarine_file() -> &'static str { "LSB Version: n/a\n\ Distributor ID: UltramarineLinux\n\ Description: Ultramarine Linux 39 (Kuma)\n\ Release: 39\n\ Codename: kuma\n\ " } fn void_file() -> &'static str { "LSB Version: n/a\n\ Distributor ID: Void\n\ Description: Void Linux\n\ Release: rolling\n\ Codename: n/a\n\ " } fn cachyos_file() -> &'static str { "Distributor ID: cachyos\n\ Description: CachyOS\n\ Release: rolling\n\ Codename: n/a\n\ " } } os_info-3.9.2/src/linux/mod.rs000064400000000000000000000032721046102023000143520ustar 00000000000000mod file_release; mod lsb_release; use log::trace; use crate::{architecture, bitness, Info, Type}; pub fn current_platform() -> Info { trace!("linux::current_platform is called"); let mut info = lsb_release::get() .or_else(file_release::get) .unwrap_or_else(|| Info::with_type(Type::Linux)); info.bitness = bitness::get(); info.architecture = architecture::get(); trace!("Returning {:?}", info); info } #[cfg(test)] mod tests { use super::*; #[test] fn os_type() { let version = current_platform(); match version.os_type() { Type::AlmaLinux | Type::Alpaquita | Type::Alpine | Type::Amazon | Type::Arch | Type::Artix | Type::CachyOS | Type::CentOS | Type::Debian | Type::EndeavourOS | Type::Fedora | Type::Garuda | Type::Gentoo | Type::Kali | Type::Linux | Type::Mabox | Type::Manjaro | Type::Mariner | Type::NixOS | Type::Nobara | Type::Uos | Type::OpenCloudOS | Type::openEuler | Type::openSUSE | Type::OracleLinux | Type::Pop | Type::Raspbian | Type::Redhat | Type::RedHatEnterprise | Type::RockyLinux | Type::Solus | Type::SUSE | Type::Ubuntu | Type::Ultramarine | Type::Void | Type::Mint => (), os_type => { panic!("Unexpected OS type: {}", os_type); } } } } os_info-3.9.2/src/macos/mod.rs000064400000000000000000000051051046102023000143120ustar 00000000000000use std::process::Command; use log::{trace, warn}; use crate::{architecture, bitness, matcher::Matcher, Info, Type, Version}; pub fn current_platform() -> Info { trace!("macos::current_platform is called"); let info = Info { os_type: Type::Macos, version: version(), bitness: bitness::get(), architecture: architecture::get(), ..Default::default() }; trace!("Returning {:?}", info); info } fn version() -> Version { match product_version() { None => Version::Unknown, Some(val) => Version::from_string(val), } } fn product_version() -> Option { match Command::new("sw_vers").output() { Ok(val) => { let output = String::from_utf8_lossy(&val.stdout); trace!("sw_vers command returned {:?}", output); parse(&output) } Err(e) => { warn!("sw_vers command failed with {:?}", e); None } } } fn parse(sw_vers_output: &str) -> Option { Matcher::PrefixedVersion { prefix: "ProductVersion:", } .find(sw_vers_output) } #[cfg(test)] mod tests { use super::*; use pretty_assertions::{assert_eq, assert_ne}; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::Macos, version.os_type()); } #[test] fn os_version() { let version = version(); assert_ne!(Version::Unknown, version); } #[test] fn string_product_version() { let version = product_version(); assert!(version.is_some()); } #[test] fn parse_version() { let parse_output = parse(sw_vers_output()); assert_eq!(parse_output, Some("10.10.5".to_string())); } fn sw_vers_output() -> &'static str { "ProductName: Mac OS X\n\ ProductVersion: 10.10.5\n\ BuildVersion: 14F27" } #[test] fn parse_beta_version() { let parse_output = parse(sw_vers_output_beta()); assert_eq!(parse_output, Some("10.15".to_string())); } fn sw_vers_output_beta() -> &'static str { "ProductName: Mac OS X\n\ ProductVersion: 10.15\n\ BuildVersion: 19A546d" } #[test] fn parse_double_digit_patch_version() { let parse_output = parse(sw_vers_output_double_digit_patch_version()); assert_eq!(parse_output, Some("10.15.21".to_string())); } fn sw_vers_output_double_digit_patch_version() -> &'static str { "ProductName: Mac OS X\n\ ProductVersion: 10.15.21\n\ BuildVersion: ABCD123" } } os_info-3.9.2/src/matcher.rs000064400000000000000000000103651046102023000140600ustar 00000000000000/// An implementation to match on simple strings. #[derive(Debug, Clone)] #[allow(dead_code)] pub enum Matcher { /// Considers the entire string (trimmed) to be the match. AllTrimmed, /// After finding the `prefix` followed by one or more spaces, returns the following word. PrefixedWord { prefix: &'static str }, /// Similar to `PrefixedWord`, but only if the word is a valid version. PrefixedVersion { prefix: &'static str }, /// Takes a set of lines (separated by `\n`) and searches for the value in a key/value pair /// separated by the `=` character. For example `VERSION_ID="8.1"`. KeyValue { key: &'static str }, } impl Matcher { /// Find the match on the input `string`. pub fn find(&self, string: &str) -> Option { match *self { Self::AllTrimmed => Some(string.trim().to_string()), Self::PrefixedWord { prefix } => find_prefixed_word(string, prefix).map(str::to_owned), Self::PrefixedVersion { prefix } => find_prefixed_word(string, prefix) .filter(|&v| is_valid_version(v)) .map(str::to_owned), Self::KeyValue { key } => find_by_key(string, key).map(str::to_owned), } } } fn find_by_key<'a>(string: &'a str, key: &str) -> Option<&'a str> { let key = [key, "="].concat(); for line in string.lines() { if line.starts_with(&key) { return Some(line[key.len()..].trim_matches(|c: char| c == '"' || c.is_whitespace())); } } None } fn find_prefixed_word<'a>(string: &'a str, prefix: &str) -> Option<&'a str> { if let Some(prefix_start) = string.find(prefix) { // Ignore prefix and leading whitespace let string = &string[prefix_start + prefix.len()..].trim_start(); // Find where the word boundary ends let word_end = string .find(|c: char| c.is_whitespace()) .unwrap_or(string.len()); let string = &string[..word_end]; Some(string) } else { None } } fn is_valid_version(word: &str) -> bool { !word.starts_with('.') && !word.ends_with('.') } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn trimmed() { let data = [ ("", Some("")), ("test", Some("test")), (" test", Some("test")), ("test ", Some("test")), (" test ", Some("test")), ]; let matcher = Matcher::AllTrimmed; for (input, expected) in &data { let result = matcher.find(input); assert_eq!(result.as_deref(), *expected); } } #[test] fn prefixed_word() { let data = [ ("", None), ("test", Some("")), ("test1", Some("1")), ("test 1", Some("1")), (" test 1", Some("1")), ("test 1.2.3", Some("1.2.3")), (" test 1.2.3", Some("1.2.3")), ]; let matcher = Matcher::PrefixedWord { prefix: "test" }; for (input, expected) in &data { let result = matcher.find(input); assert_eq!(result.as_deref(), *expected); } } #[test] fn prefixed_version() { let data = [ ("", None), ("test", Some("")), ("test 1", Some("1")), ("test .1", None), ("test 1.", None), ("test .1.", None), (" test 1", Some("1")), ("test 1.2.3", Some("1.2.3")), (" test 1.2.3", Some("1.2.3")), ]; let matcher = Matcher::PrefixedVersion { prefix: "test" }; for (input, expected) in &data { let result = matcher.find(input); assert_eq!(result.as_deref(), *expected); } } #[test] fn key_value() { let data = [ ("", None), ("key", None), ("key=value", Some("value")), ("key=1", Some("1")), ("key=\"1\"", Some("1")), ("key=\"CentOS Linux\"", Some("CentOS Linux")), ]; let matcher = Matcher::KeyValue { key: "key" }; for (input, expected) in &data { let result = matcher.find(input); assert_eq!(result.as_deref(), *expected); } } } os_info-3.9.2/src/netbsd/mod.rs000064400000000000000000000014041046102023000144650ustar 00000000000000use std::process::Command; use log::{error, trace}; use crate::{architecture, bitness, uname::uname, Info, Type, Version}; pub fn current_platform() -> Info { trace!("netbsd::current_platform is called"); let version = uname("-s") .map(Version::from_string) .unwrap_or_else(|| Version::Unknown); let info = Info { os_type: Type::NetBSD, version, bitness: bitness::get(), architecture: architecture::get(), ..Default::default() }; trace!("Returning {:?}", info); info } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::NetBSD, version.os_type()); } } os_info-3.9.2/src/openbsd/mod.rs000064400000000000000000000014071046102023000146430ustar 00000000000000use std::process::Command; use log::{error, trace}; use crate::{architecture, bitness, uname::uname, Info, Type, Version}; pub fn current_platform() -> Info { trace!("openbsd::current_platform is called"); let version = uname("-r") .map(Version::from_string) .unwrap_or_else(|| Version::Unknown); let info = Info { os_type: Type::OpenBSD, version, bitness: bitness::get(), architecture: architecture::get(), ..Default::default() }; trace!("Returning {:?}", info); info } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::OpenBSD, version.os_type()); } } os_info-3.9.2/src/os_type.rs000064400000000000000000000204051046102023000141130ustar 00000000000000use std::fmt::{self, Display, Formatter}; /// A list of supported operating system types. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[non_exhaustive] pub enum Type { /// IBM AIX (). AIX, /// AlmaLinux (). AlmaLinux, /// Alpaquita Linux (). Alpaquita, /// Alpine Linux (). Alpine, /// Amazon Linux AMI (). Amazon, /// Android (). Android, /// Arch Linux (). Arch, /// Artix Linux (). Artix, /// CachyOS (). CachyOS, /// CentOS (). CentOS, /// Debian (). Debian, /// DragonFly BSD (). DragonFly, /// Emscripten (). Emscripten, /// EndeavourOS (). EndeavourOS, /// Fedora (). Fedora, /// FreeBSD (). FreeBSD, /// Garuda Linux () Garuda, /// Gentoo Linux (). Gentoo, /// HardenedBSD (https://hardenedbsd.org/). HardenedBSD, /// Illumos (https://en.wikipedia.org/wiki/Illumos). Illumos, /// Kali Linux (https://en.wikipedia.org/wiki/Kali_Linux). Kali, /// Linux based operating system (). Linux, /// Mabox (). Mabox, /// Mac OS X/OS X/macOS (). Macos, /// Manjaro (). Manjaro, /// Mariner (). Mariner, /// MidnightBSD (). MidnightBSD, /// Mint (). Mint, /// NetBSD (). NetBSD, /// NixOS (). NixOS, /// Nobara (). Nobara, /// OpenBSD (). OpenBSD, /// OpenCloudOS (). OpenCloudOS, /// openEuler (). openEuler, /// openSUSE (). openSUSE, /// Oracle Linux (). OracleLinux, /// Pop!_OS () Pop, /// Raspberry Pi OS (). Raspbian, /// Red Hat Linux (). Redhat, /// Red Hat Enterprise Linux (). RedHatEnterprise, /// Redox (). Redox, /// Rocky Linux (). RockyLinux, /// Solus (). Solus, /// SUSE Linux Enterprise Server (). SUSE, /// Ubuntu (). Ubuntu, /// Ultramarine (). Ultramarine, /// Uos (). Uos, /// Void Linux (). Void, /// Unknown operating system. Unknown, /// Windows (). Windows, } impl Default for Type { fn default() -> Self { Type::Unknown } } impl Display for Type { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match *self { Type::Alpaquita => write!(f, "Alpaquita Linux"), Type::Alpine => write!(f, "Alpine Linux"), Type::AlmaLinux => write!(f, "AlmaLinux"), Type::Amazon => write!(f, "Amazon Linux AMI"), Type::Arch => write!(f, "Arch Linux"), Type::CachyOS => write!(f, "CachyOS Linux"), Type::Artix => write!(f, "Artix Linux"), Type::DragonFly => write!(f, "DragonFly BSD"), Type::Garuda => write!(f, "Garuda Linux"), Type::Gentoo => write!(f, "Gentoo Linux"), Type::Illumos => write!(f, "illumos"), Type::Kali => write!(f, "Kali Linux"), Type::Macos => write!(f, "Mac OS"), Type::MidnightBSD => write!(f, "Midnight BSD"), Type::Mint => write!(f, "Linux Mint"), Type::Nobara => write!(f, "Nobara Linux"), Type::openEuler => write!(f, "EulerOS"), Type::OracleLinux => write!(f, "Oracle Linux"), Type::Pop => write!(f, "Pop!_OS"), Type::Raspbian => write!(f, "Raspberry Pi OS"), Type::Redhat => write!(f, "Red Hat Linux"), Type::RedHatEnterprise => write!(f, "Red Hat Enterprise Linux"), Type::RockyLinux => write!(f, "Rocky Linux"), Type::SUSE => write!(f, "SUSE Linux Enterprise Server"), Type::Ultramarine => write!(f, "Ultramarine Linux"), Type::Uos => write!(f, "UOS"), Type::Void => write!(f, "Void Linux"), _ => write!(f, "{self:?}"), } } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn default() { assert_eq!(Type::Unknown, Type::default()); } #[test] fn display() { let data = [ (Type::AIX, "AIX"), (Type::AlmaLinux, "AlmaLinux"), (Type::Alpaquita, "Alpaquita Linux"), (Type::Alpine, "Alpine Linux"), (Type::Amazon, "Amazon Linux AMI"), (Type::Android, "Android"), (Type::Arch, "Arch Linux"), (Type::Artix, "Artix Linux"), (Type::CachyOS, "CachyOS Linux"), (Type::CentOS, "CentOS"), (Type::Debian, "Debian"), (Type::DragonFly, "DragonFly BSD"), (Type::Emscripten, "Emscripten"), (Type::EndeavourOS, "EndeavourOS"), (Type::Fedora, "Fedora"), (Type::FreeBSD, "FreeBSD"), (Type::Garuda, "Garuda Linux"), (Type::Gentoo, "Gentoo Linux"), (Type::HardenedBSD, "HardenedBSD"), (Type::Illumos, "illumos"), (Type::Kali, "Kali Linux"), (Type::Linux, "Linux"), (Type::Mabox, "Mabox"), (Type::Macos, "Mac OS"), (Type::Manjaro, "Manjaro"), (Type::Mariner, "Mariner"), (Type::MidnightBSD, "Midnight BSD"), (Type::Mint, "Linux Mint"), (Type::NetBSD, "NetBSD"), (Type::NixOS, "NixOS"), (Type::Nobara, "Nobara Linux"), (Type::OpenCloudOS, "OpenCloudOS"), (Type::OpenBSD, "OpenBSD"), (Type::openEuler, "EulerOS"), (Type::openSUSE, "openSUSE"), (Type::OracleLinux, "Oracle Linux"), (Type::Pop, "Pop!_OS"), (Type::Raspbian, "Raspberry Pi OS"), (Type::Redhat, "Red Hat Linux"), (Type::RedHatEnterprise, "Red Hat Enterprise Linux"), (Type::Redox, "Redox"), (Type::RockyLinux, "Rocky Linux"), (Type::Solus, "Solus"), (Type::SUSE, "SUSE Linux Enterprise Server"), (Type::Ubuntu, "Ubuntu"), (Type::Ultramarine, "Ultramarine Linux"), (Type::Unknown, "Unknown"), (Type::Uos, "UOS"), (Type::Void, "Void Linux"), (Type::Windows, "Windows"), ]; for (t, expected) in &data { assert_eq!(&t.to_string(), expected); } } } os_info-3.9.2/src/redox/mod.rs000064400000000000000000000023051046102023000143300ustar 00000000000000// spell-checker:ignore uname use std::{fs::File, io::Read}; use log::{error, trace}; use crate::{Bitness, Info, Type, Version}; const UNAME_FILE: &str = "sys:uname"; pub fn current_platform() -> Info { trace!("redox::current_platform is called"); let version = get_version() .map(Version::from_string) .unwrap_or_else(|| Version::Unknown); let info = Info { os_type: Type::Redox, version, bitness: Bitness::Unknown, ..Default::default() }; trace!("Returning {:?}", info); info } fn get_version() -> Option { let mut file = match File::open(UNAME_FILE) { Ok(file) => file, Err(e) => { error!("Unable to open {} file: {:?}", UNAME_FILE, e); return None; } }; let mut version = String::new(); if let Err(e) = file.read_to_string(&mut version) { error!("Unable to read {} file: {:?}", UNAME_FILE, e); return None; } Some(version) } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::Redox, version.os_type()); } } os_info-3.9.2/src/uname.rs000064400000000000000000000013251046102023000135360ustar 00000000000000use std::process::Command; use log::error; pub fn uname(arg: &str) -> Option { Command::new("uname") .arg(arg) .output() .map_err(|e| { error!("Failed to invoke 'uname {}': {:?}", arg, e); }) .ok() .and_then(|out| { if out.status.success() { Some(String::from_utf8_lossy(&out.stdout).trim_end().to_owned()) } else { log::error!("'uname' invocation error: {:?}", out); None } }) } #[cfg(test)] mod tests { use super::*; #[test] fn uname_nonempty() { let val = uname("-s").expect("uname failed"); assert!(!val.is_empty()); } } os_info-3.9.2/src/unknown/mod.rs000064400000000000000000000005621046102023000147110ustar 00000000000000use log::trace; use crate::{Info, Type}; pub fn current_platform() -> Info { trace!("unknown::current_platform is called"); Info::unknown() } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::Unknown, version.os_type()); } } os_info-3.9.2/src/version.rs000064400000000000000000000103111046102023000141110ustar 00000000000000use std::fmt::{self, Display, Formatter}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Operating system version. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Version { /// Unknown version. Unknown, /// Semantic version (major.minor.patch). Semantic(u64, u64, u64), /// Rolling version. Optionally contains the release date in the string format. Rolling(Option), /// Custom version format. Custom(String), } impl Version { /// Constructs `VersionType` from the given string. /// /// Returns `VersionType::Unknown` if the string is empty. If it can be parsed as a semantic /// version, then `VersionType::Semantic`, otherwise `VersionType::Custom`. /// /// # Examples /// /// ``` /// use os_info::Version; /// /// let v = Version::from_string("custom"); /// assert_eq!(Version::Custom("custom".to_owned()), v); /// /// let v = Version::from_string("1.2.3"); /// assert_eq!(Version::Semantic(1, 2, 3), v); /// ``` pub fn from_string + AsRef>(s: S) -> Self { if s.as_ref().is_empty() { Self::Unknown } else if let Some((major, minor, patch)) = parse_version(s.as_ref()) { Self::Semantic(major, minor, patch) } else { Self::Custom(s.into()) } } } impl Default for Version { fn default() -> Self { Version::Unknown } } impl Display for Version { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match *self { Self::Unknown => f.write_str("Unknown"), Self::Semantic(major, minor, patch) => write!(f, "{major}.{minor}.{patch}"), Self::Rolling(ref date) => { let date = match date { Some(date) => format!(" ({date})"), None => "".to_owned(), }; write!(f, "Rolling Release{date}") } Self::Custom(ref version) => write!(f, "{version}"), } } } fn parse_version(s: &str) -> Option<(u64, u64, u64)> { let mut iter = s.trim().split_terminator('.').fuse(); let major = iter.next().and_then(|s| s.parse().ok())?; let minor = iter.next().unwrap_or("0").parse().ok()?; let patch = iter.next().unwrap_or("0").parse().ok()?; if iter.next().is_some() { return None; } Some((major, minor, patch)) } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn parse_semantic_version() { let data = [ ("", None), ("version", None), ("1", Some((1, 0, 0))), ("1.", Some((1, 0, 0))), ("1.2", Some((1, 2, 0))), ("1.2.", Some((1, 2, 0))), ("1.2.3", Some((1, 2, 3))), ("1.2.3.", Some((1, 2, 3))), ("1.2.3. ", Some((1, 2, 3))), (" 1.2.3.", Some((1, 2, 3))), (" 1.2.3. ", Some((1, 2, 3))), ("1.2.3.4", None), ("1.2.3.4.5.6.7.8.9", None), ]; for (s, expected) in &data { let result = parse_version(s); assert_eq!(expected, &result); } } #[test] fn from_string() { let custom_version = "some version"; let data = [ ("", Version::Unknown), ("1.2.3", Version::Semantic(1, 2, 3)), (custom_version, Version::Custom(custom_version.to_owned())), ]; for (s, expected) in &data { let version = Version::from_string(*s); assert_eq!(expected, &version); } } #[test] fn default() { assert_eq!(Version::Unknown, Version::default()); } #[test] fn display() { let data = [ (Version::Unknown, "Unknown"), (Version::Semantic(1, 5, 0), "1.5.0"), (Version::Rolling(None), "Rolling Release"), ( Version::Rolling(Some("date".to_owned())), "Rolling Release (date)", ), ]; for (version, expected) in &data { assert_eq!(expected, &version.to_string()); } } } os_info-3.9.2/src/windows/mod.rs000064400000000000000000000007611046102023000147050ustar 00000000000000mod winapi; use log::trace; use crate::Info; pub fn current_platform() -> Info { trace!("windows::current_platform is called"); let info = winapi::get(); trace!("Returning {:?}", info); info } #[cfg(test)] mod tests { use super::*; use crate::Type; use pretty_assertions::assert_eq; #[test] fn os_type() { let version = current_platform(); assert_eq!(Type::Windows, version.os_type()); assert!(version.edition().is_some()); } } os_info-3.9.2/src/windows/winapi.rs000064400000000000000000000321711046102023000154150ustar 00000000000000// spell-checker:ignore dword, minwindef, ntdef, ntdll, ntstatus, osversioninfoex, osversioninfoexa // spell-checker:ignore osversioninfoexw, serverr, sysinfoapi, winnt, winuser, pbool, libloaderapi // spell-checker:ignore lpcstr, processthreadsapi, farproc, lstatus, wchar, lpbyte, hkey, winerror // spell-checker:ignore osstr, winreg #![allow(unsafe_code)] use std::{ ffi::{OsStr, OsString}, mem::{self, MaybeUninit}, os::windows::ffi::{OsStrExt, OsStringExt}, ptr, }; use windows_sys::Win32::{ Foundation::{ERROR_SUCCESS, FARPROC, NTSTATUS, STATUS_SUCCESS}, System::{ LibraryLoader::{GetModuleHandleA, GetProcAddress}, Registry::{RegOpenKeyExW, RegQueryValueExW, HKEY_LOCAL_MACHINE, KEY_READ, REG_SZ}, SystemInformation::{ GetNativeSystemInfo, GetSystemInfo, PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_ARM, PROCESSOR_ARCHITECTURE_IA64, PROCESSOR_ARCHITECTURE_INTEL, SYSTEM_INFO, }, SystemServices::{VER_NT_WORKSTATION, VER_SUITE_WH_SERVER}, }, UI::WindowsAndMessaging::{GetSystemMetrics, SM_SERVERR2}, }; use crate::{Bitness, Info, Type, Version}; #[cfg(target_arch = "x86")] type OSVERSIONINFOEX = windows_sys::Win32::System::SystemInformation::OSVERSIONINFOEXA; #[cfg(not(target_arch = "x86"))] type OSVERSIONINFOEX = windows_sys::Win32::System::SystemInformation::OSVERSIONINFOEXW; pub fn get() -> Info { let (version, edition) = version(); let native_system_info = native_system_info(); Info { os_type: Type::Windows, version, edition, bitness: bitness(), architecture: architecture(native_system_info), ..Default::default() } } fn version() -> (Version, Option) { match version_info() { None => (Version::Unknown, None), Some(v) => ( Version::Semantic( v.dwMajorVersion as u64, v.dwMinorVersion as u64, v.dwBuildNumber as u64, ), product_name(&v).or_else(|| edition(&v)), ), } } // According to https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info // there is a variant for AMD64 CPUs, but it's not defined in generated bindings. const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12; fn native_system_info() -> SYSTEM_INFO { let mut system_info: MaybeUninit = MaybeUninit::zeroed(); unsafe { GetNativeSystemInfo(system_info.as_mut_ptr()); }; unsafe { system_info.assume_init() } } fn architecture(system_info: SYSTEM_INFO) -> Option { let cpu_architecture = unsafe { system_info.Anonymous.Anonymous.wProcessorArchitecture }; match cpu_architecture { PROCESSOR_ARCHITECTURE_AMD64 => Some("x86_64"), PROCESSOR_ARCHITECTURE_IA64 => Some("ia64"), PROCESSOR_ARCHITECTURE_ARM => Some("arm"), PROCESSOR_ARCHITECTURE_ARM64 => Some("aarch64"), PROCESSOR_ARCHITECTURE_INTEL => Some("i386"), _ => None, } .map(str::to_string) } #[cfg(target_pointer_width = "64")] fn bitness() -> Bitness { // x64 program can only run on x64 Windows. Bitness::X64 } #[cfg(target_pointer_width = "32")] fn bitness() -> Bitness { use windows_sys::Win32::Foundation::{BOOL, FALSE, HANDLE}; use windows_sys::Win32::System::Threading::GetCurrentProcess; // IsWow64Process is not available on all supported versions of Windows. Use GetModuleHandle to // get a handle to the DLL that contains the function and GetProcAddress to get a pointer to the // function if available. let is_wow_64 = match get_proc_address(b"kernel32\0", b"IsWow64Process\0") { None => return Bitness::Unknown, Some(val) => val, }; type IsWow64 = unsafe extern "system" fn(HANDLE, *mut BOOL) -> BOOL; let is_wow_64: IsWow64 = unsafe { mem::transmute(is_wow_64) }; let mut result = FALSE; if unsafe { is_wow_64(GetCurrentProcess(), &mut result) } == 0 { log::error!("IsWow64Process failed"); return Bitness::Unknown; } if result == FALSE { Bitness::X32 } else { Bitness::X64 } } // Calls the Win32 API function RtlGetVersion to get the OS version information: // https://msdn.microsoft.com/en-us/library/mt723418(v=vs.85).aspx fn version_info() -> Option { let rtl_get_version = match get_proc_address(b"ntdll\0", b"RtlGetVersion\0") { None => return None, Some(val) => val, }; type RtlGetVersion = unsafe extern "system" fn(&mut OSVERSIONINFOEX) -> NTSTATUS; let rtl_get_version: RtlGetVersion = unsafe { mem::transmute(rtl_get_version) }; let mut info: OSVERSIONINFOEX = unsafe { mem::zeroed() }; info.dwOSVersionInfoSize = mem::size_of::() as u32; if unsafe { rtl_get_version(&mut info) } == STATUS_SUCCESS { Some(info) } else { None } } fn product_name(info: &OSVERSIONINFOEX) -> Option { let sub_key = to_wide("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); let mut key = Default::default(); if unsafe { RegOpenKeyExW(HKEY_LOCAL_MACHINE, sub_key.as_ptr(), 0, KEY_READ, &mut key) } != ERROR_SUCCESS || key == 0 { log::error!("RegOpenKeyExW(HKEY_LOCAL_MACHINE, ...) failed"); return None; } let is_win_11 = info.dwMajorVersion == 10 && info.dwBuildNumber >= 22000; // Get size of the data. let name = to_wide(if is_win_11 { "EditionID" } else { "ProductName" }); let mut data_type = 0; let mut data_size = 0; if unsafe { RegQueryValueExW( key, name.as_ptr(), ptr::null_mut(), &mut data_type, ptr::null_mut(), &mut data_size, ) } != ERROR_SUCCESS || data_type != REG_SZ || data_size == 0 || data_size % 2 != 0 { log::error!("RegQueryValueExW failed"); return None; } // Get the data. let mut data = vec![0u16; data_size as usize / 2]; if unsafe { RegQueryValueExW( key, name.as_ptr(), ptr::null_mut(), ptr::null_mut(), data.as_mut_ptr().cast(), &mut data_size, ) } != ERROR_SUCCESS || data_size as usize != data.len() * 2 { return None; } // If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been // stored with the proper terminating null characters. match data.last() { Some(0) => { data.pop(); } _ => {} } let value = OsString::from_wide(data.as_slice()) .to_string_lossy() .into_owned(); if is_win_11 { Some(format!("Windows 11 {}", value)) } else { Some(value) } } fn to_wide(value: &str) -> Vec { OsStr::new(value).encode_wide().chain(Some(0)).collect() } // Examines data in the OSVERSIONINFOEX structure to determine the Windows edition: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx fn edition(version_info: &OSVERSIONINFOEX) -> Option { match ( version_info.dwMajorVersion, version_info.dwMinorVersion, version_info.wProductType as u32, ) { // Windows 10. (10, 0, VER_NT_WORKSTATION) => { if version_info.dwBuildNumber >= 22000 { Some("Windows 11") } else { Some("Windows 10") } } (10, 0, _) => Some("Windows Server 2016"), // Windows Vista, 7, 8 and 8.1. (6, 3, VER_NT_WORKSTATION) => Some("Windows 8.1"), (6, 3, _) => Some("Windows Server 2012 R2"), (6, 2, VER_NT_WORKSTATION) => Some("Windows 8"), (6, 2, _) => Some("Windows Server 2012"), (6, 1, VER_NT_WORKSTATION) => Some("Windows 7"), (6, 1, _) => Some("Windows Server 2008 R2"), (6, 0, VER_NT_WORKSTATION) => Some("Windows Vista"), (6, 0, _) => Some("Windows Server 2008"), // Windows 2000, Home Server, 2003 Server, 2003 R2 Server, XP and XP Professional x64. (5, 1, _) => Some("Windows XP"), (5, 0, _) => Some("Windows 2000"), (5, 2, _) if unsafe { GetSystemMetrics(SM_SERVERR2) } == 0 => { let mut info: SYSTEM_INFO = unsafe { mem::zeroed() }; unsafe { GetSystemInfo(&mut info) }; if Into::::into(version_info.wSuiteMask) & VER_SUITE_WH_SERVER == VER_SUITE_WH_SERVER { Some("Windows Home Server") } else if version_info.wProductType == VER_NT_WORKSTATION as u8 && unsafe { info.Anonymous.Anonymous.wProcessorArchitecture } == PROCESSOR_ARCHITECTURE_AMD64 { Some("Windows XP Professional x64 Edition") } else { Some("Windows Server 2003") } } _ => None, } .map(str::to_string) } fn get_proc_address(module: &[u8], proc: &[u8]) -> Option { assert!( *module.last().expect("Empty module name") == 0, "Module name should be zero-terminated" ); assert!( *proc.last().expect("Empty procedure name") == 0, "Procedure name should be zero-terminated" ); let handle = unsafe { GetModuleHandleA(module.as_ptr()) }; if handle == 0 { log::error!( "GetModuleHandleA({}) failed", String::from_utf8_lossy(module) ); return None; } unsafe { Some(GetProcAddress(handle, proc.as_ptr())) } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::{assert_eq, assert_ne}; #[test] fn version() { let info = get(); assert_eq!(Type::Windows, info.os_type()); } #[test] fn get_version_info() { let version = version_info(); assert!(version.is_some()); } #[test] fn get_edition() { let test_data = [ (10, 0, 0, "Windows Server 2016"), (6, 3, VER_NT_WORKSTATION, "Windows 8.1"), (6, 3, 0, "Windows Server 2012 R2"), (6, 2, VER_NT_WORKSTATION, "Windows 8"), (6, 2, 0, "Windows Server 2012"), (6, 1, VER_NT_WORKSTATION, "Windows 7"), (6, 1, 0, "Windows Server 2008 R2"), (6, 0, VER_NT_WORKSTATION, "Windows Vista"), (6, 0, 0, "Windows Server 2008"), (5, 1, 0, "Windows XP"), (5, 1, 1, "Windows XP"), (5, 1, 100, "Windows XP"), (5, 0, 0, "Windows 2000"), (5, 0, 1, "Windows 2000"), (5, 0, 100, "Windows 2000"), ]; let mut info = version_info().unwrap(); for &(major, minor, product_type, expected_edition) in &test_data { info.dwMajorVersion = major; info.dwMinorVersion = minor; info.wProductType = product_type as u8; let edition = edition(&info).unwrap(); assert_eq!(edition, expected_edition); } } #[test] fn get_bitness() { let b = bitness(); assert_ne!(b, Bitness::Unknown); } #[test] #[should_panic(expected = "Empty module name")] fn empty_module_name() { get_proc_address(b"", b"RtlGetVersion\0"); } #[test] #[should_panic(expected = "Module name should be zero-terminated")] fn non_zero_terminated_module_name() { get_proc_address(b"ntdll", b"RtlGetVersion\0"); } #[test] #[should_panic(expected = "Empty procedure name")] fn empty_proc_name() { get_proc_address(b"ntdll\0", b""); } #[test] #[should_panic(expected = "Procedure name should be zero-terminated")] fn non_zero_terminated_proc_name() { get_proc_address(b"ntdll\0", b"RtlGetVersion"); } #[test] fn proc_address() { let address = get_proc_address(b"ntdll\0", b"RtlGetVersion\0"); assert!(address.is_some()); } #[test] fn get_architecture() { let cpu_types: [(u16, Option); 6] = [ (PROCESSOR_ARCHITECTURE_AMD64, Some("x86_64".to_owned())), (PROCESSOR_ARCHITECTURE_ARM, Some("arm".to_owned())), (PROCESSOR_ARCHITECTURE_ARM64, Some("aarch64".to_owned())), (PROCESSOR_ARCHITECTURE_IA64, Some("ia64".to_owned())), (PROCESSOR_ARCHITECTURE_INTEL, Some("i386".to_owned())), (0xffff, None), ]; let mut native_info = native_system_info(); for cpu_type in cpu_types { native_info.Anonymous.Anonymous.wProcessorArchitecture = cpu_type.0; assert_eq!(architecture(native_info), cpu_type.1); } } #[test] fn get_product_name() { let version = version_info().expect("version_info() failed"); let edition = product_name(&version).expect("edition() failed"); assert!(!edition.is_empty()); } #[test] fn to_wide_str() { let data = [ ("", [0x0000].as_ref()), ("U", &[0x0055, 0x0000]), ("你好", &[0x4F60, 0x597D, 0x0000]), ]; for (s, expected) in &data { let wide = to_wide(s); assert_eq!(&wide, expected); } } }