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_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()
});
}
None
}
#[derive(Clone)]
struct ReleaseInfo<'a> {
path: &'a str,
os_type: for<'b> fn(&'b str) -> Option<Type>,
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()
}
}
static DISTRIBUTIONS: [ReleaseInfo; 6] = [
ReleaseInfo {
path: "etc/os-release",
os_type: |release| {
Matcher::KeyValue { key: "ID" }
.find(release)
.and_then(|id| match id.as_str() {
"alpine" => Some(Type::Alpine),
"amzn" => Some(Type::Amazon),
"arch" => Some(Type::Arch),
"centos" => Some(Type::CentOS),
"fedora" => Some(Type::Fedora),
"linuxmint" => Some(Type::Mint),
"mariner" => Some(Type::Mariner),
"nixos" => Some(Type::NixOS),
"ol" => Some(Type::OracleLinux),
"opensuse" => Some(Type::openSUSE),
"opensuse-leap" => Some(Type::openSUSE),
"rhel" => Some(Type::RedHatEnterprise),
"sled" => Some(Type::SUSE), "sles" => Some(Type::SUSE),
"sles_sap" => Some(Type::SUSE), "ubuntu" => Some(Type::Ubuntu),
_ => None,
})
},
version: |release| {
Matcher::KeyValue { key: "VERSION_ID" }
.find(release)
.map(Version::from_string)
},
},
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]);
}
}