diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/os_info/src | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip |
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/os_info/src')
48 files changed, 3132 insertions, 0 deletions
diff --git a/vendor/os_info/src/android/mod.rs b/vendor/os_info/src/android/mod.rs new file mode 100644 index 000000000..51c99ba88 --- /dev/null +++ b/vendor/os_info/src/android/mod.rs @@ -0,0 +1,23 @@ +use 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()); + } +} diff --git a/vendor/os_info/src/bitness.rs b/vendor/os_info/src/bitness.rs new file mode 100644 index 000000000..dba7e1c83 --- /dev/null +++ b/vendor/os_info/src/bitness.rs @@ -0,0 +1,124 @@ +// spell-checker:ignore getconf + +use std::fmt::{self, Display, Formatter}; +#[cfg(any( + 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(all( + test, + any( + 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); + } + } +} diff --git a/vendor/os_info/src/dragonfly/mod.rs b/vendor/os_info/src/dragonfly/mod.rs new file mode 100644 index 000000000..63fe0e7c4 --- /dev/null +++ b/vendor/os_info/src/dragonfly/mod.rs @@ -0,0 +1,35 @@ +use 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() + .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()); + } +} diff --git a/vendor/os_info/src/emscripten/mod.rs b/vendor/os_info/src/emscripten/mod.rs new file mode 100644 index 000000000..36be37706 --- /dev/null +++ b/vendor/os_info/src/emscripten/mod.rs @@ -0,0 +1,24 @@ +use 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()); + } +} diff --git a/vendor/os_info/src/freebsd/mod.rs b/vendor/os_info/src/freebsd/mod.rs new file mode 100644 index 000000000..09c8dbe88 --- /dev/null +++ b/vendor/os_info/src/freebsd/mod.rs @@ -0,0 +1,60 @@ +use 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() + .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 { + let os = Command::new("uname") + .arg("-s") + .output() + .expect("Failed to get OS"); + + match str::from_utf8(&os.stdout) { + Ok("FreeBSD\n") => { + let check_hardening = Command::new("sysctl") + .arg("hardening.version") + .output() + .expect("Failed to check if is hardened"); + match str::from_utf8(&check_hardening.stderr) { + Ok("0\n") => Type::HardenedBSD, + Ok(_) => Type::FreeBSD, + Err(_) => Type::FreeBSD, + } + } + Ok("MidnightBSD\n") => Type::MidnightBSD, + Ok(_) => Type::Unknown, + Err(_) => 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()); + } +} diff --git a/vendor/os_info/src/illumos/mod.rs b/vendor/os_info/src/illumos/mod.rs new file mode 100644 index 000000000..1f23771f1 --- /dev/null +++ b/vendor/os_info/src/illumos/mod.rs @@ -0,0 +1,67 @@ +use 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 = 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<String> { + Command::new("uname") + .arg("-v") + .output() + .map_err(|e| { + error!("Failed to invoke 'uname': {:?}", 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 + } + }) +} + +fn get_os() -> Type { + let os = Command::new("uname") + .arg("-o") + .output() + .expect("Failed to get OS"); + + match str::from_utf8(&os.stdout) { + Ok("illumos\n") => Type::Illumos, + Ok(_) => Type::Unknown, + Err(_) => 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()); + } +} diff --git a/vendor/os_info/src/info.rs b/vendor/os_info/src/info.rs new file mode 100644 index 000000000..85b699330 --- /dev/null +++ b/vendor/os_info/src/info.rs @@ -0,0 +1,311 @@ +// 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<String>, + /// Operating system edition. + pub(crate) codename: Option<String>, + /// 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, +} + +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()); + /// ``` + pub fn unknown() -> Self { + Self { + os_type: Type::Unknown, + version: Version::Unknown, + edition: None, + codename: None, + bitness: Bitness::Unknown, + } + } + + /// 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()); + /// ``` + 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 + } +} + +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()); + } + + #[test] + fn with_type() { + let types = [ + Type::Redox, + Type::Alpine, + Type::Amazon, + Type::Android, + Type::Arch, + Type::CentOS, + Type::Debian, + Type::Emscripten, + Type::EndeavourOS, + Type::Fedora, + Type::Gentoo, + Type::Linux, + Type::Macos, + Type::Manjaro, + Type::Mariner, + Type::NixOS, + Type::openSUSE, + Type::OracleLinux, + Type::Pop, + Type::Redhat, + Type::RedHatEnterprise, + Type::Redox, + Type::Solus, + Type::SUSE, + Type::Ubuntu, + 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::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, + }, + "Mac OS 10.2.0 (edition) (codename) [64-bit]", + ), + ]; + + for (info, expected) in &data { + assert_eq!(expected, &info.to_string()); + } + } +} diff --git a/vendor/os_info/src/lib.rs b/vendor/os_info/src/lib.rs new file mode 100644 index 000000000..a1a6746ec --- /dev/null +++ b/vendor/os_info/src/lib.rs @@ -0,0 +1,111 @@ +//! `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 = "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 = "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; + +mod bitness; +mod info; +#[cfg(not(windows))] +mod matcher; +mod os_type; +#[cfg(any( + 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()); +/// ``` +pub fn get() -> Info { + imp::current_platform() +} diff --git a/vendor/os_info/src/linux/file_release.rs b/vendor/os_info/src/linux/file_release.rs new file mode 100644 index 000000000..119c85560 --- /dev/null +++ b/vendor/os_info/src/linux/file_release.rs @@ -0,0 +1,493 @@ +// 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<Info> { + retrieve(&DISTRIBUTIONS, "/") +} + +fn retrieve(distributions: &[ReleaseInfo], root: &str) -> Option<Info> { + 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 { + 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<Type>, + + /// A closure that determines the os version from the release file contents. + version: for<'b> fn(&'b str) -> Option<Version>, +} + +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<Type>)) + .field("version", &(self.version as fn(&'a str) -> Option<Version>)) + .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" => Alma + "alpine" => Some(Type::Alpine), + "amzn" => Some(Type::Amazon), + //"antergos" => Antergos + //"aosc" => AOSC + "arch" => Some(Type::Arch), + //"artix" => Artix + "centos" => Some(Type::CentOS), + //"clear-linux-os" => ClearLinuxOS + //"clearos" => ClearOS + //"coreos" + //"cumulus-linux" => Cumulus + //"debian" => Debian + //"devuan" => Devuan + //"elementary" => Elementary + "fedora" => Some(Type::Fedora), + //"gentoo" => Gentoo + //"ios_xr" => ios_xr + //"kali" => Kali + //"mageia" => Mageia + //"manjaro" => Manjaro + "linuxmint" => Some(Type::Mint), + "mariner" => Some(Type::Mariner), + //"nexus" => Nexus + "nixos" => Some(Type::NixOS), + "ol" => Some(Type::OracleLinux), + "opensuse" => Some(Type::openSUSE), + "opensuse-leap" => Some(Type::openSUSE), + //"rancheros" => RancherOS + //"raspbian" => Raspbian + // note XBian also uses "raspbian" + "rhel" => Some(Type::RedHatEnterprise), + //"rocky" => Rocky + //"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), + //"virtuozzo" => Virtuozzo + //"void" => 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 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 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 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 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 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 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 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 release_info_debug() { + dbg!("{:?}", &DISTRIBUTIONS[0]); + } +} diff --git a/vendor/os_info/src/linux/lsb_release.rs b/vendor/os_info/src/linux/lsb_release.rs new file mode 100644 index 000000000..58a96ae2a --- /dev/null +++ b/vendor/os_info/src/linux/lsb_release.rs @@ -0,0 +1,482 @@ +// 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<Info> { + let release = retrieve()?; + + let version = match release.version.as_deref() { + Some("rolling") => Version::Rolling(None), + Some(v) => Version::Custom(v.to_owned()), + None => Version::Unknown, + }; + + let os_type = match release.distribution.as_ref().map(String::as_ref) { + Some("Amazon") | Some("AmazonAMI") => Type::Amazon, + Some("Arch") => Type::Arch, + 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("Linuxmint") => Type::Mint, + Some("ManjaroLinux") => Type::Manjaro, + Some("Mariner") => Type::Mariner, + Some("NixOS") => Type::NixOS, + 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, + _ => Type::Linux, + }; + + Some(Info { + os_type, + version, + codename: release.codename, + ..Default::default() + }) +} + +struct LsbRelease { + pub distribution: Option<String>, + pub version: Option<String>, + pub codename: Option<String>, +} + +fn retrieve() -> Option<LsbRelease> { + 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 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 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 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 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 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); + } + + fn file() -> &'static str { + "\nDistributor ID: Debian\n\ + Description: Debian GNU/Linux 7.8 (wheezy)\n\ + Release: 7.8\n\ + Codename: wheezy\n\ + " + } + + 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 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 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" + } + + // 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\ + " + } +} diff --git a/vendor/os_info/src/linux/mod.rs b/vendor/os_info/src/linux/mod.rs new file mode 100644 index 000000000..64d488342 --- /dev/null +++ b/vendor/os_info/src/linux/mod.rs @@ -0,0 +1,56 @@ +mod file_release; +mod lsb_release; + +use log::trace; + +use crate::{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(); + + trace!("Returning {:?}", info); + info +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn os_type() { + let version = current_platform(); + match version.os_type() { + Type::Alpine + | Type::Amazon + | Type::Arch + | Type::CentOS + | Type::Debian + | Type::EndeavourOS + | Type::Fedora + | Type::Garuda + | Type::Gentoo + | Type::Linux + | Type::Manjaro + | Type::Mariner + | Type::NixOS + | Type::openSUSE + | Type::OracleLinux + | Type::Pop + | Type::Raspbian + | Type::Redhat + | Type::RedHatEnterprise + | Type::Solus + | Type::SUSE + | Type::Ubuntu + | Type::Mint => (), + os_type => { + panic!("Unexpected OS type: {}", os_type); + } + } + } +} diff --git a/vendor/os_info/src/linux/tests/Alpine/etc/alpine-release b/vendor/os_info/src/linux/tests/Alpine/etc/alpine-release new file mode 100644 index 000000000..86562312c --- /dev/null +++ b/vendor/os_info/src/linux/tests/Alpine/etc/alpine-release @@ -0,0 +1 @@ +A.B.C diff --git a/vendor/os_info/src/linux/tests/Alpine_3_12/etc/os-release b/vendor/os_info/src/linux/tests/Alpine_3_12/etc/os-release new file mode 100644 index 000000000..c2e6bc68a --- /dev/null +++ b/vendor/os_info/src/linux/tests/Alpine_3_12/etc/os-release @@ -0,0 +1,6 @@ +NAME="Alpine Linux" +ID=alpine +VERSION_ID=3.12.0 +PRETTY_NAME="Alpine Linux v3.12" +HOME_URL="https://alpinelinux.org/" +BUG_REPORT_URL="https://bugs.alpinelinux.org/" diff --git a/vendor/os_info/src/linux/tests/Amazon_1/etc/os-release b/vendor/os_info/src/linux/tests/Amazon_1/etc/os-release new file mode 100644 index 000000000..b2b1a0796 --- /dev/null +++ b/vendor/os_info/src/linux/tests/Amazon_1/etc/os-release @@ -0,0 +1,9 @@ +NAME="Amazon Linux AMI" +VERSION="2018.03" +ID="amzn" +ID_LIKE="rhel fedora" +VERSION_ID="2018.03" +PRETTY_NAME="Amazon Linux AMI 2018.03" +ANSI_COLOR="0;33" +CPE_NAME="cpe:/o:amazon:linux:2018.03:ga" +HOME_URL="http://aws.amazon.com/amazon-linux-ami/" diff --git a/vendor/os_info/src/linux/tests/Amazon_2/etc/os-release b/vendor/os_info/src/linux/tests/Amazon_2/etc/os-release new file mode 100644 index 000000000..07a45072f --- /dev/null +++ b/vendor/os_info/src/linux/tests/Amazon_2/etc/os-release @@ -0,0 +1,9 @@ +NAME="Amazon Linux" +VERSION="2" +ID="amzn" +ID_LIKE="centos rhel fedora" +VERSION_ID="2" +PRETTY_NAME="Amazon Linux 2" +ANSI_COLOR="0;33" +CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2" +HOME_URL="https://amazonlinux.com/" diff --git a/vendor/os_info/src/linux/tests/CentOS/etc/centos-release b/vendor/os_info/src/linux/tests/CentOS/etc/centos-release new file mode 100644 index 000000000..359ef00dc --- /dev/null +++ b/vendor/os_info/src/linux/tests/CentOS/etc/centos-release @@ -0,0 +1 @@ +Centos Linux release XX diff --git a/vendor/os_info/src/linux/tests/CentOS_7/etc/os-release b/vendor/os_info/src/linux/tests/CentOS_7/etc/os-release new file mode 100644 index 000000000..c276e3ae5 --- /dev/null +++ b/vendor/os_info/src/linux/tests/CentOS_7/etc/os-release @@ -0,0 +1,15 @@ +NAME="CentOS Linux" +VERSION="7 (Core)" +ID="centos" +ID_LIKE="rhel fedora" +VERSION_ID="7" +PRETTY_NAME="CentOS Linux 7 (Core)" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:centos:centos:7" +HOME_URL="https://www.centos.org/" +BUG_REPORT_URL="https://bugs.centos.org/" + +CENTOS_MANTISBT_PROJECT="CentOS-7" +CENTOS_MANTISBT_PROJECT_VERSION="7" +REDHAT_SUPPORT_PRODUCT="centos" +REDHAT_SUPPORT_PRODUCT_VERSION="7" diff --git a/vendor/os_info/src/linux/tests/CentOS_Stream/etc/os-release b/vendor/os_info/src/linux/tests/CentOS_Stream/etc/os-release new file mode 100644 index 000000000..8948e12f4 --- /dev/null +++ b/vendor/os_info/src/linux/tests/CentOS_Stream/etc/os-release @@ -0,0 +1,13 @@ +NAME="CentOS Stream" +VERSION="8" +ID="centos" +ID_LIKE="rhel fedora" +VERSION_ID="8" +PLATFORM_ID="platform:el8" +PRETTY_NAME="CentOS Stream 8" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:centos:centos:8" +HOME_URL="https://centos.org/" +BUG_REPORT_URL="https://bugzilla.redhat.com/" +REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux 8" +REDHAT_SUPPORT_PRODUCT_VERSION="CentOS Stream" diff --git a/vendor/os_info/src/linux/tests/CentOS_Unknown/etc/centos-release b/vendor/os_info/src/linux/tests/CentOS_Unknown/etc/centos-release new file mode 100644 index 000000000..e6b865bc7 --- /dev/null +++ b/vendor/os_info/src/linux/tests/CentOS_Unknown/etc/centos-release @@ -0,0 +1 @@ +Centos Linux diff --git a/vendor/os_info/src/linux/tests/Fedora/etc/fedora-release b/vendor/os_info/src/linux/tests/Fedora/etc/fedora-release new file mode 100644 index 000000000..f2641f4b5 --- /dev/null +++ b/vendor/os_info/src/linux/tests/Fedora/etc/fedora-release @@ -0,0 +1 @@ +Fedora release 26 (Twenty Six) diff --git a/vendor/os_info/src/linux/tests/Fedora_32/etc/os-release b/vendor/os_info/src/linux/tests/Fedora_32/etc/os-release new file mode 100644 index 000000000..234ecf163 --- /dev/null +++ b/vendor/os_info/src/linux/tests/Fedora_32/etc/os-release @@ -0,0 +1,21 @@ +NAME=Fedora +VERSION="32 (Cloud Edition)" +ID=fedora +VERSION_ID=32 +VERSION_CODENAME="" +PLATFORM_ID="platform:f32" +PRETTY_NAME="Fedora 32 (Cloud Edition)" +ANSI_COLOR="0;34" +LOGO=fedora-logo-icon +CPE_NAME="cpe:/o:fedoraproject:fedora:32" +HOME_URL="https://fedoraproject.org/" +DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f32/system-administrators-guide/" +SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help" +BUG_REPORT_URL="https://bugzilla.redhat.com/" +REDHAT_BUGZILLA_PRODUCT="Fedora" +REDHAT_BUGZILLA_PRODUCT_VERSION=32 +REDHAT_SUPPORT_PRODUCT="Fedora" +REDHAT_SUPPORT_PRODUCT_VERSION=32 +PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy" +VARIANT="Cloud Edition" +VARIANT_ID=cloud diff --git a/vendor/os_info/src/linux/tests/Fedora_35/etc/os-release b/vendor/os_info/src/linux/tests/Fedora_35/etc/os-release new file mode 100644 index 000000000..9178d6d38 --- /dev/null +++ b/vendor/os_info/src/linux/tests/Fedora_35/etc/os-release @@ -0,0 +1,21 @@ +NAME="Fedora Linux" +VERSION="35 (Workstation Edition)" +ID=fedora +VERSION_ID=35 +VERSION_CODENAME="" +PLATFORM_ID="platform:f35" +PRETTY_NAME="Fedora Linux 35 (Workstation Edition)" +ANSI_COLOR="0;38;2;60;110;180" +LOGO=fedora-logo-icon +CPE_NAME="cpe:/o:fedoraproject:fedora:35" +HOME_URL="https://fedoraproject.org/" +DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f35/system-administrators-guide/" +SUPPORT_URL="https://ask.fedoraproject.org/" +BUG_REPORT_URL="https://bugzilla.redhat.com/" +REDHAT_BUGZILLA_PRODUCT="Fedora" +REDHAT_BUGZILLA_PRODUCT_VERSION=35 +REDHAT_SUPPORT_PRODUCT="Fedora" +REDHAT_SUPPORT_PRODUCT_VERSION=35 +PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy" +VARIANT="Workstation Edition" +VARIANT_ID=workstation diff --git a/vendor/os_info/src/linux/tests/Fedora_Unknown/etc/fedora-release b/vendor/os_info/src/linux/tests/Fedora_Unknown/etc/fedora-release new file mode 100644 index 000000000..adc01153f --- /dev/null +++ b/vendor/os_info/src/linux/tests/Fedora_Unknown/etc/fedora-release @@ -0,0 +1 @@ +Fedora (Foo Bar) diff --git a/vendor/os_info/src/linux/tests/Mariner/etc/mariner-release b/vendor/os_info/src/linux/tests/Mariner/etc/mariner-release new file mode 100644 index 000000000..f4bceb49e --- /dev/null +++ b/vendor/os_info/src/linux/tests/Mariner/etc/mariner-release @@ -0,0 +1,2 @@ +CBL-Mariner 2.0.20220210 +MARINER_BUILD_NUMBER=0d11c66 diff --git a/vendor/os_info/src/linux/tests/Mariner_Unknown/etc/mariner-release b/vendor/os_info/src/linux/tests/Mariner_Unknown/etc/mariner-release new file mode 100644 index 000000000..d64fd5303 --- /dev/null +++ b/vendor/os_info/src/linux/tests/Mariner_Unknown/etc/mariner-release @@ -0,0 +1 @@ +CBL-Mariner
\ No newline at end of file diff --git a/vendor/os_info/src/linux/tests/Mint/etc/os-release b/vendor/os_info/src/linux/tests/Mint/etc/os-release new file mode 100644 index 000000000..825d92877 --- /dev/null +++ b/vendor/os_info/src/linux/tests/Mint/etc/os-release @@ -0,0 +1,12 @@ +NAME="Linux Mint" +VERSION="20 (Ulyana)" +ID=linuxmint +ID_LIKE=ubuntu +PRETTY_NAME="Linux Mint 20" +VERSION_ID="20" +HOME_URL="https://www.linuxmint.com/" +SUPPORT_URL="https://forums.linuxmint.com/" +BUG_REPORT_URL="http://linuxmint-troubleshooting-guide.readthedocs.io/en/latest/" +PRIVACY_POLICY_URL="https://www.linuxmint.com/" +VERSION_CODENAME=ulyana +UBUNTU_CODENAME=focal diff --git a/vendor/os_info/src/linux/tests/NixOS/etc/os-release b/vendor/os_info/src/linux/tests/NixOS/etc/os-release new file mode 100644 index 000000000..b93cf4d12 --- /dev/null +++ b/vendor/os_info/src/linux/tests/NixOS/etc/os-release @@ -0,0 +1,11 @@ +NAME=NixOS +ID=nixos +VERSION="21.05pre275822.916ee862e87 (Okapi)" +VERSION_CODENAME=okapi +VERSION_ID="21.05pre275822.916ee862e87" +PRETTY_NAME="NixOS 21.05 (Okapi)" +LOGO="nix-snowflake" +HOME_URL="https://nixos.org/" +DOCUMENTATION_URL="https://nixos.org/learn.html" +SUPPORT_URL="https://nixos.org/community.html" +BUG_REPORT_URL="https://github.com/NixOS/nixpkgs/issues" diff --git a/vendor/os_info/src/linux/tests/OracleLinux/etc/os-release b/vendor/os_info/src/linux/tests/OracleLinux/etc/os-release new file mode 100644 index 000000000..1594788f3 --- /dev/null +++ b/vendor/os_info/src/linux/tests/OracleLinux/etc/os-release @@ -0,0 +1,18 @@ +NAME="Oracle Linux Server" +VERSION="8.1" +ID="ol" +ID_LIKE="fedora" +VARIANT="Server" +VARIANT_ID="server" +VERSION_ID="8.1" +PLATFORM_ID="platform:el8" +PRETTY_NAME="Oracle Linux Server 8.1" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:oracle:linux:8:1:server" +HOME_URL="https://linux.oracle.com/" +BUG_REPORT_URL="https://bugzilla.oracle.com/" + +ORACLE_BUGZILLA_PRODUCT="Oracle Linux 8" +ORACLE_BUGZILLA_PRODUCT_VERSION=8.1 +ORACLE_SUPPORT_PRODUCT="Oracle Linux" +ORACLE_SUPPORT_PRODUCT_VERSION=8.1 diff --git a/vendor/os_info/src/linux/tests/OracleLinux/etc/redhat-release b/vendor/os_info/src/linux/tests/OracleLinux/etc/redhat-release new file mode 100644 index 000000000..994041ed8 --- /dev/null +++ b/vendor/os_info/src/linux/tests/OracleLinux/etc/redhat-release @@ -0,0 +1 @@ +Redhat Linux release XX
\ No newline at end of file diff --git a/vendor/os_info/src/linux/tests/RedHatEnterprise/etc/redhat-release b/vendor/os_info/src/linux/tests/RedHatEnterprise/etc/redhat-release new file mode 100644 index 000000000..ff4da1dea --- /dev/null +++ b/vendor/os_info/src/linux/tests/RedHatEnterprise/etc/redhat-release @@ -0,0 +1 @@ +Redhat Linux release XX diff --git a/vendor/os_info/src/linux/tests/RedHatEnterprise_7/etc/os-release b/vendor/os_info/src/linux/tests/RedHatEnterprise_7/etc/os-release new file mode 100644 index 000000000..ec038b12a --- /dev/null +++ b/vendor/os_info/src/linux/tests/RedHatEnterprise_7/etc/os-release @@ -0,0 +1,17 @@ +NAME="Red Hat Enterprise Linux Server" +VERSION="7.9 (Maipo)" +ID="rhel" +ID_LIKE="fedora" +VARIANT="Server" +VARIANT_ID="server" +VERSION_ID="7.9" +PRETTY_NAME="Red Hat Enterprise Linux Server 7.9 (Maipo)" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:redhat:enterprise_linux:7.9:GA:server" +HOME_URL="https://www.redhat.com/" +BUG_REPORT_URL="https://bugzilla.redhat.com/" + +REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7" +REDHAT_BUGZILLA_PRODUCT_VERSION=7.9 +REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux" +REDHAT_SUPPORT_PRODUCT_VERSION="7.9" diff --git a/vendor/os_info/src/linux/tests/RedHatEnterprise_8/etc/os-release b/vendor/os_info/src/linux/tests/RedHatEnterprise_8/etc/os-release new file mode 100644 index 000000000..1d5515ba6 --- /dev/null +++ b/vendor/os_info/src/linux/tests/RedHatEnterprise_8/etc/os-release @@ -0,0 +1,16 @@ +NAME="Red Hat Enterprise Linux" +VERSION="8.2 (Ootpa)" +ID="rhel" +ID_LIKE="fedora" +VERSION_ID="8.2" +PLATFORM_ID="platform:el8" +PRETTY_NAME="Red Hat Enterprise Linux 8.2 (Ootpa)" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:redhat:enterprise_linux:8.2:GA" +HOME_URL="https://www.redhat.com/" +BUG_REPORT_URL="https://bugzilla.redhat.com/" + +REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8" +REDHAT_BUGZILLA_PRODUCT_VERSION=8.2 +REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux" +REDHAT_SUPPORT_PRODUCT_VERSION="8.2" diff --git a/vendor/os_info/src/linux/tests/RedHatEnterprise_Unknown/etc/redhat-release b/vendor/os_info/src/linux/tests/RedHatEnterprise_Unknown/etc/redhat-release new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vendor/os_info/src/linux/tests/RedHatEnterprise_Unknown/etc/redhat-release diff --git a/vendor/os_info/src/linux/tests/SUSE_12/etc/os-release b/vendor/os_info/src/linux/tests/SUSE_12/etc/os-release new file mode 100644 index 000000000..2de3f28ed --- /dev/null +++ b/vendor/os_info/src/linux/tests/SUSE_12/etc/os-release @@ -0,0 +1,7 @@ +NAME="SLES" +VERSION="12-SP5" +VERSION_ID="12.5" +PRETTY_NAME="SUSE Linux Enterprise Server 12 SP5" +ID="sles" +ANSI_COLOR="0;32" +CPE_NAME="cpe:/o:suse:sles:12:sp5" diff --git a/vendor/os_info/src/linux/tests/SUSE_15/etc/os-release b/vendor/os_info/src/linux/tests/SUSE_15/etc/os-release new file mode 100644 index 000000000..54fafb50b --- /dev/null +++ b/vendor/os_info/src/linux/tests/SUSE_15/etc/os-release @@ -0,0 +1,8 @@ +NAME="SLES" +VERSION="15-SP2" +VERSION_ID="15.2" +PRETTY_NAME="SUSE Linux Enterprise Server 15 SP2" +ID="sles" +ID_LIKE="suse" +ANSI_COLOR="0;32" +CPE_NAME="cpe:/o:suse:sles:15:sp2" diff --git a/vendor/os_info/src/linux/tests/Ubuntu/etc/os-release b/vendor/os_info/src/linux/tests/Ubuntu/etc/os-release new file mode 100644 index 000000000..2369e58a0 --- /dev/null +++ b/vendor/os_info/src/linux/tests/Ubuntu/etc/os-release @@ -0,0 +1,12 @@ +NAME="Ubuntu" +VERSION="18.10 (Cosmic Cuttlefish)" +ID=ubuntu +ID_LIKE=debian +PRETTY_NAME="Ubuntu 18.10" +VERSION_ID="18.10" +HOME_URL="https://www.ubuntu.com/" +SUPPORT_URL="https://help.ubuntu.com/" +BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" +PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" +VERSION_CODENAME=cosmic +UBUNTU_CODENAME=cosmic diff --git a/vendor/os_info/src/linux/tests/none_invalid_os_release/etc/os-release b/vendor/os_info/src/linux/tests/none_invalid_os_release/etc/os-release new file mode 100644 index 000000000..92258e650 --- /dev/null +++ b/vendor/os_info/src/linux/tests/none_invalid_os_release/etc/os-release @@ -0,0 +1,2 @@ +ID="Lorem ipsum dolor sit amet" +VERSION_ID="It's all greek to me" diff --git a/vendor/os_info/src/macos/mod.rs b/vendor/os_info/src/macos/mod.rs new file mode 100644 index 000000000..0c93a9713 --- /dev/null +++ b/vendor/os_info/src/macos/mod.rs @@ -0,0 +1,106 @@ +use std::process::Command; + +use log::{trace, warn}; + +use crate::{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(), + ..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<String> { + 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<String> { + 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" + } +} diff --git a/vendor/os_info/src/matcher.rs b/vendor/os_info/src/matcher.rs new file mode 100644 index 000000000..fcc31bc59 --- /dev/null +++ b/vendor/os_info/src/matcher.rs @@ -0,0 +1,148 @@ +/// 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<String> { + 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); + } + } +} diff --git a/vendor/os_info/src/netbsd/mod.rs b/vendor/os_info/src/netbsd/mod.rs new file mode 100644 index 000000000..d5ab97bfb --- /dev/null +++ b/vendor/os_info/src/netbsd/mod.rs @@ -0,0 +1,35 @@ +use std::process::Command; + +use log::{error, trace}; + +use crate::{bitness, uname::uname, Info, Type, Version}; + +pub fn current_platform() -> Info { + trace!("netbsd::current_platform is called"); + + let version = uname() + .map(Version::from_string) + .unwrap_or_else(|| Version::Unknown); + + let info = Info { + os_type: Type::NetBSD, + 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::NetBSD, version.os_type()); + } +} diff --git a/vendor/os_info/src/openbsd/mod.rs b/vendor/os_info/src/openbsd/mod.rs new file mode 100644 index 000000000..897fc4aeb --- /dev/null +++ b/vendor/os_info/src/openbsd/mod.rs @@ -0,0 +1,35 @@ +use std::process::Command; + +use log::{error, trace}; + +use crate::{bitness, uname::uname, Info, Type, Version}; + +pub fn current_platform() -> Info { + trace!("openbsd::current_platform is called"); + + let version = uname() + .map(Version::from_string) + .unwrap_or_else(|| Version::Unknown); + + let info = Info { + os_type: Type::OpenBSD, + 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::OpenBSD, version.os_type()); + } +} diff --git a/vendor/os_info/src/os_type.rs b/vendor/os_info/src/os_type.rs new file mode 100644 index 000000000..8cab07b8e --- /dev/null +++ b/vendor/os_info/src/os_type.rs @@ -0,0 +1,162 @@ +use 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 { + /// Alpine Linux (<https://en.wikipedia.org/wiki/Alpine_Linux>). + Alpine, + /// Amazon Linux AMI (<https://en.wikipedia.org/wiki/Amazon_Machine_Image#Amazon_Linux_AMI>). + Amazon, + /// Android (<https://en.wikipedia.org/wiki/Android_(operating_system)>). + Android, + /// Arch Linux (<https://en.wikipedia.org/wiki/Arch_Linux>). + Arch, + /// CentOS (<https://en.wikipedia.org/wiki/CentOS>). + CentOS, + /// Debian (<https://en.wikipedia.org/wiki/Debian>). + Debian, + /// DragonFly BSD (<https://en.wikipedia.org/wiki/DragonFly_BSD>). + DragonFly, + /// Emscripten (<https://en.wikipedia.org/wiki/Emscripten>). + Emscripten, + /// EndeavourOS (<https://en.wikipedia.org/wiki/EndeavourOS>). + EndeavourOS, + /// Fedora (<https://en.wikipedia.org/wiki/Fedora_(operating_system)>). + Fedora, + /// FreeBSD (<https://en.wikipedia.org/wiki/FreeBSD>). + FreeBSD, + /// Garuda Linux (<https://en.wikipedia.org/wiki/Garuda_Linux>) + Garuda, + /// Gentoo Linux (<https://en.wikipedia.org/wiki/Gentoo_Linux>). + Gentoo, + /// HardenedBSD (https://hardenedbsd.org/). + HardenedBSD, + /// Illumos (https://en.wikipedia.org/wiki/Illumos). + Illumos, + /// Linux based operating system (<https://en.wikipedia.org/wiki/Linux>). + Linux, + /// Mac OS X/OS X/macOS (<https://en.wikipedia.org/wiki/MacOS>). + Macos, + /// Manjaro (<https://en.wikipedia.org/wiki/Manjaro>). + Manjaro, + /// Mariner (<https://en.wikipedia.org/wiki/CBL-Mariner>). + Mariner, + /// MidnightBSD (<https://en.wikipedia.org/wiki/MidnightBSD>). + MidnightBSD, + /// Mint (<https://en.wikipedia.org/wiki/Linux_Mint>). + Mint, + /// NetBSD (<https://en.wikipedia.org/wiki/NetBSD>). + NetBSD, + /// NixOS (<https://en.wikipedia.org/wiki/NixOS>). + NixOS, + /// OpenBSD (<https://en.wikipedia.org/wiki/OpenBSD>). + OpenBSD, + /// openSUSE (<https://en.wikipedia.org/wiki/OpenSUSE>). + openSUSE, + /// Oracle Linux (<https://en.wikipedia.org/wiki/Oracle_Linux>). + OracleLinux, + /// Pop!_OS (<https://en.wikipedia.org/wiki/Pop!_OS>) + Pop, + /// Raspberry Pi OS (<https://en.wikipedia.org/wiki/Raspberry_Pi_OS>). + Raspbian, + /// Red Hat Linux (<https://en.wikipedia.org/wiki/Red_Hat_Linux>). + Redhat, + /// Red Hat Enterprise Linux (<https://en.wikipedia.org/wiki/Red_Hat_Enterprise_Linux>). + RedHatEnterprise, + /// Redox (<https://en.wikipedia.org/wiki/Redox_(operating_system)>). + Redox, + /// Solus (<https://en.wikipedia.org/wiki/Solus_(operating_system)>). + Solus, + /// SUSE Linux Enterprise Server (<https://en.wikipedia.org/wiki/SUSE_Linux_Enterprise>). + SUSE, + /// Ubuntu (<https://en.wikipedia.org/wiki/Ubuntu_(operating_system)>). + Ubuntu, + /// Unknown operating system. + Unknown, + /// Windows (<https://en.wikipedia.org/wiki/Microsoft_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::Alpine => write!(f, "Alpine Linux"), + Type::Amazon => write!(f, "Amazon Linux AMI"), + Type::Arch => write!(f, "Arch 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::Macos => write!(f, "Mac OS"), + Type::MidnightBSD => write!(f, "Midnight BSD"), + Type::Mint => write!(f, "Linux Mint"), + 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::SUSE => write!(f, "SUSE Linux Enterprise Server"), + _ => 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::Alpine, "Alpine Linux"), + (Type::Amazon, "Amazon Linux AMI"), + (Type::Android, "Android"), + (Type::Arch, "Arch Linux"), + (Type::CentOS, "CentOS"), + (Type::Debian, "Debian"), + (Type::DragonFly, "DragonFly BSD"), + (Type::Emscripten, "Emscripten"), + (Type::EndeavourOS, "EndeavourOS"), + (Type::Fedora, "Fedora"), + (Type::Garuda, "Garuda Linux"), + (Type::Gentoo, "Gentoo Linux"), + (Type::FreeBSD, "FreeBSD"), + (Type::Linux, "Linux"), + (Type::Macos, "Mac OS"), + (Type::Manjaro, "Manjaro"), + (Type::Mint, "Linux Mint"), + (Type::NetBSD, "NetBSD"), + (Type::NixOS, "NixOS"), + (Type::OpenBSD, "OpenBSD"), + (Type::openSUSE, "openSUSE"), + (Type::OracleLinux, "OracleLinux"), + (Type::Pop, "Pop!_OS"), + (Type::Raspbian, "Raspberry Pi OS"), + (Type::Redhat, "Red Hat Linux"), + (Type::RedHatEnterprise, "Red Hat Enterprise Linux"), + (Type::Redox, "Redox"), + (Type::Solus, "Solus"), + (Type::SUSE, "SUSE Linux Enterprise Server"), + (Type::Ubuntu, "Ubuntu"), + (Type::Unknown, "Unknown"), + (Type::Windows, "Windows"), + ]; + + for (t, expected) in &data { + assert_eq!(&t.to_string(), expected); + } + } +} diff --git a/vendor/os_info/src/redox/mod.rs b/vendor/os_info/src/redox/mod.rs new file mode 100644 index 000000000..1efd59113 --- /dev/null +++ b/vendor/os_info/src/redox/mod.rs @@ -0,0 +1,54 @@ +// 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<String> { + 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()); + } +} diff --git a/vendor/os_info/src/uname.rs b/vendor/os_info/src/uname.rs new file mode 100644 index 000000000..f4417e882 --- /dev/null +++ b/vendor/os_info/src/uname.rs @@ -0,0 +1,32 @@ +use std::process::Command; + +use log::error; + +pub fn uname() -> Option<String> { + Command::new("uname") + .arg("-r") + .output() + .map_err(|e| { + error!("Failed to invoke 'uname': {:?}", 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().expect("uname failed"); + assert!(!val.is_empty()); + } +} diff --git a/vendor/os_info/src/unknown/mod.rs b/vendor/os_info/src/unknown/mod.rs new file mode 100644 index 000000000..fbebe213d --- /dev/null +++ b/vendor/os_info/src/unknown/mod.rs @@ -0,0 +1,20 @@ +use 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()); + } +} diff --git a/vendor/os_info/src/version.rs b/vendor/os_info/src/version.rs new file mode 100644 index 000000000..20a2c4a61 --- /dev/null +++ b/vendor/os_info/src/version.rs @@ -0,0 +1,150 @@ +use 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<String>), + /// 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<S: Into<String> + AsRef<str>>(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()); + } + } +} diff --git a/vendor/os_info/src/windows/mod.rs b/vendor/os_info/src/windows/mod.rs new file mode 100644 index 000000000..4e57d4a62 --- /dev/null +++ b/vendor/os_info/src/windows/mod.rs @@ -0,0 +1,26 @@ +mod 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()); + } +} diff --git a/vendor/os_info/src/windows/winapi.rs b/vendor/os_info/src/windows/winapi.rs new file mode 100644 index 000000000..59e3f30d9 --- /dev/null +++ b/vendor/os_info/src/windows/winapi.rs @@ -0,0 +1,371 @@ +// 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, + os::windows::ffi::{OsStrExt, OsStringExt}, + ptr, +}; + +use winapi::{ + shared::{ + minwindef::{DWORD, FARPROC, LPBYTE}, + ntdef::{LPCSTR, NTSTATUS}, + ntstatus::STATUS_SUCCESS, + winerror::ERROR_SUCCESS, + }, + um::{ + libloaderapi::{GetModuleHandleA, GetProcAddress}, + sysinfoapi::{GetSystemInfo, SYSTEM_INFO}, + winnt::{ + KEY_READ, PROCESSOR_ARCHITECTURE_AMD64, REG_SZ, VER_NT_WORKSTATION, + VER_SUITE_WH_SERVER, WCHAR, + }, + winreg::{RegOpenKeyExW, RegQueryValueExW, HKEY_LOCAL_MACHINE, LSTATUS}, + winuser::{GetSystemMetrics, SM_SERVERR2}, + }, +}; + +use crate::{Bitness, Info, Type, Version}; + +#[cfg(target_arch = "x86")] +type OSVERSIONINFOEX = winapi::um::winnt::OSVERSIONINFOEXA; + +#[cfg(not(target_arch = "x86"))] +type OSVERSIONINFOEX = winapi::um::winnt::OSVERSIONINFOEXW; + +pub fn get() -> Info { + let (version, edition) = version(); + Info { + os_type: Type::Windows, + version, + edition, + bitness: bitness(), + ..Default::default() + } +} + +fn version() -> (Version, Option<String>) { + 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().or_else(|| edition(&v)), + ), + } +} + +#[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 winapi::{ + shared::{ + minwindef::{BOOL, FALSE, PBOOL}, + ntdef::HANDLE, + }, + um::processthreadsapi::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, PBOOL) -> 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<OSVERSIONINFOEX> { + 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::<OSVERSIONINFOEX>() as DWORD; + + if unsafe { rtl_get_version(&mut info) } == STATUS_SUCCESS { + Some(info) + } else { + None + } +} + +fn product_name() -> Option<String> { + const REG_SUCCESS: LSTATUS = ERROR_SUCCESS as LSTATUS; + + let sub_key = to_wide("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); + let mut key = ptr::null_mut(); + if unsafe { RegOpenKeyExW(HKEY_LOCAL_MACHINE, sub_key.as_ptr(), 0, KEY_READ, &mut key) } + != REG_SUCCESS + || key.is_null() + { + log::error!("RegOpenKeyExW(HKEY_LOCAL_MACHINE, ...) failed"); + return None; + } + + // Get size of the data. + let name = to_wide("ProductName"); + let mut data_type: DWORD = 0; + let mut data_size: DWORD = 0; + if unsafe { + RegQueryValueExW( + key, + name.as_ptr(), + ptr::null_mut(), + &mut data_type, + ptr::null_mut(), + &mut data_size, + ) + } != REG_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() as LPBYTE, + &mut data_size, + ) + } != REG_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(); + } + _ => {} + } + + Some( + OsString::from_wide(data.as_slice()) + .to_string_lossy() + .into_owned(), + ) +} + +fn to_wide(value: &str) -> Vec<WCHAR> { + 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<String> { + match ( + version_info.dwMajorVersion, + version_info.dwMinorVersion, + version_info.wProductType, + ) { + // Windows 10. + (10, 0, VER_NT_WORKSTATION) => 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::<DWORD>::into(version_info.wSuiteMask) & VER_SUITE_WH_SERVER + == VER_SUITE_WH_SERVER + { + Some("Windows Home Server") + } else if version_info.wProductType == VER_NT_WORKSTATION + && unsafe { info.u.s().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<FARPROC> { + 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() as LPCSTR) }; + if handle.is_null() { + log::error!( + "GetModuleHandleA({}) failed", + String::from_utf8_lossy(module) + ); + return None; + } + + unsafe { Some(GetProcAddress(handle, proc.as_ptr() as LPCSTR)) } +} + +#[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, VER_NT_WORKSTATION, "Windows 10"), + (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; + + 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_product_name() { + let edition = product_name().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); + } + } +} |