// 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 { 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" => Alma "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), "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" => Kali //"mageia" => Mageia //"manjaro" => Manjaro "linuxmint" => Some(Type::Mint), "mariner" => Some(Type::Mariner), //"nexus" => Nexus "nixos" => Some(Type::NixOS), "opencloudos" => Some(Type::OpenCloudOS), "openEuler" => Some(Type::openEuler), "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 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 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 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 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]); } }