etc_os_release/
construct.rs

1use std::{
2    convert::Infallible,
3    fs::File,
4    io::{self, BufRead, BufReader},
5    path::{Path, PathBuf},
6    str::FromStr,
7};
8
9use crate::{entry::OsReleaseLine, OsRelease, OsReleaseEntry};
10
11/// Errors that can occur while parsing the os-release file.
12#[derive(Debug, thiserror::Error)]
13#[non_exhaustive]
14pub enum Error {
15    /// The os-release file was not found.
16    #[error("no os-release file found")]
17    NoOsRelease,
18    /// The os-release file could not be opened.
19    #[error("failed to open os-release file: {err:?}")]
20    Open {
21        /// The path to the os-release file.
22        path: PathBuf,
23        /// The error that occurred while opening the file.
24        #[source]
25        err: std::io::Error,
26    },
27    /// The os-release file could not be read.
28    #[error("failed to read os-release file: {err:?}")]
29    Read {
30        /// The error that occurred while reading the file.
31        #[source]
32        err: std::io::Error,
33    },
34}
35
36/// Methods to construct an `OsRelease`.
37impl OsRelease {
38    /// Open the os-release file and parse it.
39    ///
40    /// If `/etc/os-release` exists, it is opened.
41    /// Otherwise, `/usr/lib/os-release` is opened.
42    /// If neither file exists, an error is returned.
43    ///
44    /// For simplicity, this function assumes that the file is well-formed.
45    pub fn open() -> Result<Self, Error> {
46        let path = os_release_path().ok_or(Error::NoOsRelease)?;
47        let file = File::open(path).map_err(|err| Error::Open {
48            path: path.to_owned(),
49            err,
50        })?;
51        Self::from_reader(file)
52    }
53
54    /// Parse the os-release file from a reader.
55    ///
56    /// For simplicity, this function assumes that the file is well-formed.
57    pub fn from_reader(reader: impl io::Read) -> Result<Self, Error> {
58        let reader = BufReader::new(reader);
59        reader
60            .lines()
61            .collect::<Result<_, _>>()
62            .map_err(|err| Error::Read { err })
63    }
64}
65
66impl<'a> FromIterator<OsReleaseEntry<'a>> for OsRelease {
67    fn from_iter<T>(iter: T) -> Self
68    where
69        T: IntoIterator<Item = OsReleaseEntry<'a>>,
70    {
71        Self {
72            fields: iter
73                .into_iter()
74                .map(|entry| (entry.key().to_owned(), entry.value().to_owned()))
75                .collect(),
76        }
77    }
78}
79
80impl<'a> FromIterator<&'a str> for OsRelease {
81    fn from_iter<T>(iter: T) -> Self
82    where
83        T: IntoIterator<Item = &'a str>,
84    {
85        iter.into_iter()
86            .filter_map(|line| {
87                OsReleaseLine::from_str(line)
88                    .ok()
89                    .and_then(|line| line.into_entry())
90            })
91            .collect()
92    }
93}
94
95impl FromIterator<String> for OsRelease {
96    fn from_iter<T>(iter: T) -> Self
97    where
98        T: IntoIterator<Item = String>,
99    {
100        iter.into_iter()
101            .filter_map(|line| {
102                OsReleaseLine::from_str(&line)
103                    .ok()
104                    .and_then(|line| line.into_entry())
105            })
106            .collect()
107    }
108}
109
110impl FromStr for OsRelease {
111    type Err = Infallible;
112
113    fn from_str(s: &str) -> Result<Self, Self::Err> {
114        Ok(s.lines().collect())
115    }
116}
117
118/// Find the os-release file to parse.
119fn os_release_path() -> Option<&'static Path> {
120    [
121        Path::new("/etc/os-release"),
122        Path::new("/usr/lib/os-release"),
123    ]
124    .into_iter()
125    .find(|path| path.exists())
126}