alpm_mtree/
schema.rs

1//! Schemas for ALPM-MTREE data.
2
3use std::{
4    fmt::{Display, Formatter},
5    fs::File,
6    io::{BufReader, Read},
7    path::{Path, PathBuf},
8    str::FromStr,
9};
10
11use alpm_common::FileFormatSchema;
12use alpm_types::{SchemaVersion, semver_version::Version};
13
14use crate::{Error, mtree_buffer_to_string};
15
16/// An enum tracking all available [ALPM-MTREE] schemas.
17///
18/// The schema of a ALPM-MTREE refers to its available fields in a specific version.
19///
20/// [ALPM-MTREE]: https://alpm.archlinux.page/specifications/ALPM-MTREE.5.html
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub enum MtreeSchema {
23    /// The [ALPM-MTREEv1] file format.
24    ///
25    /// [ALPM-MTREEv1]: https://alpm.archlinux.page/specifications/ALPM-MTREEv1.5.html
26    V1(SchemaVersion),
27    /// The [ALPM-MTREEv2] file format.
28    ///
29    /// [ALPM-MTREEv2]: https://alpm.archlinux.page/specifications/ALPM-MTREEv2.5.html
30    V2(SchemaVersion),
31}
32
33impl FileFormatSchema for MtreeSchema {
34    type Err = Error;
35
36    /// Returns a reference to the inner [`SchemaVersion`].
37    fn inner(&self) -> &SchemaVersion {
38        match self {
39            MtreeSchema::V1(v) | MtreeSchema::V2(v) => v,
40        }
41    }
42
43    /// Derives an [`MtreeSchema`] from an ALPM-MTREE file.
44    ///
45    /// Opens the `file` and defers to [`MtreeSchema::derive_from_reader`].
46    ///
47    /// # Errors
48    ///
49    /// Returns an error if
50    /// - opening `file` for reading fails
51    /// - or deriving a [`MtreeSchema`] from the contents of `file` fails.
52    fn derive_from_file(file: impl AsRef<Path>) -> Result<Self, Error>
53    where
54        Self: Sized,
55    {
56        let file = file.as_ref();
57        Self::derive_from_reader(File::open(file).map_err(|source| {
58            Error::IoPath(
59                PathBuf::from(file),
60                "deriving schema version from ALPM-MTREE file",
61                source,
62            )
63        })?)
64    }
65
66    /// Derives an [`MtreeSchema`] from ALPM-MTREE data in a `reader`.
67    ///
68    /// Reads the `reader` to string and defers to [`MtreeSchema::derive_from_str`].
69    ///
70    /// # Errors
71    ///
72    /// Returns an error if
73    /// - reading a [`String`] from `reader` fails
74    /// - or deriving a [`MtreeSchema`] from the contents of `reader` fails.
75    fn derive_from_reader(reader: impl std::io::Read) -> Result<Self, Error>
76    where
77        Self: Sized,
78    {
79        let mut buffer = Vec::new();
80        let mut buf_reader = BufReader::new(reader);
81        buf_reader
82            .read_to_end(&mut buffer)
83            .map_err(|source| Error::Io("reading ALPM-MTREE data", source))?;
84        Self::derive_from_str(&mtree_buffer_to_string(buffer)?)
85    }
86
87    /// Derives an [`MtreeSchema`] from a string slice containing ALPM-MTREE data.
88    ///
89    /// Since the ALPM-MTREE format does not carry any version information, this function checks
90    /// whether `s` contains `md5=` or `md5digest=`.
91    /// If it does, the input is considered to be [ALPM-MTREEv2].
92    /// If the strings are not found, [ALPM-MTREEv1] is assumed.
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use alpm_common::FileFormatSchema;
98    /// use alpm_mtree::MtreeSchema;
99    /// use alpm_types::{SchemaVersion, semver_version::Version};
100    ///
101    /// # fn main() -> Result<(), alpm_mtree::Error> {
102    /// let mtree_v2 = r#"
103    /// #mtree
104    /// /set mode=644 uid=0 gid=0 type=file
105    /// ./some_file time=1700000000.0 size=1337 sha256digest=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
106    /// ./some_link type=link link=some_file time=1700000000.0
107    /// ./some_dir type=dir time=1700000000.0
108    /// "#;
109    /// assert_eq!(
110    ///     MtreeSchema::V2(SchemaVersion::new(Version::new(2, 0, 0))),
111    ///     MtreeSchema::derive_from_str(mtree_v2)?
112    /// );
113    ///
114    /// let mtree_v1 = r#"
115    /// #mtree
116    /// /set mode=644 uid=0 gid=0 type=file
117    /// ./some_file time=1700000000.0 size=1337 sha256digest=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef md5digest=d3b07384d113edec49eaa6238ad5ff00
118    /// ./some_link type=link link=some_file time=1700000000.0
119    /// ./some_dir type=dir time=1700000000.0
120    /// "#;
121    /// assert_eq!(
122    ///     MtreeSchema::V1(SchemaVersion::new(Version::new(1, 0, 0))),
123    ///     MtreeSchema::derive_from_str(mtree_v1)?
124    /// );
125    /// # Ok(())
126    /// # }
127    /// ```
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if
132    /// - the first `xdata` keyword is assigned an empty string,
133    /// - or the first `xdata` keyword does not assign "pkgtype".
134    ///
135    /// [ALPM-MTREEv1]: https://alpm.archlinux.page/specifications/ALPM-MTREEv1.5.html
136    /// [ALPM-MTREEv2]: https://alpm.archlinux.page/specifications/ALPM-MTREEv2.5.html
137    fn derive_from_str(s: &str) -> Result<MtreeSchema, Error> {
138        Ok(if s.contains("md5digest=") || s.contains("md5=") {
139            MtreeSchema::V1(SchemaVersion::new(Version::new(1, 0, 0)))
140        } else {
141            MtreeSchema::V2(SchemaVersion::new(Version::new(2, 0, 0)))
142        })
143    }
144}
145
146impl Default for MtreeSchema {
147    /// Returns the default [`MtreeSchema`] variant ([`MtreeSchema::V2`]).
148    fn default() -> Self {
149        Self::V2(SchemaVersion::new(Version::new(2, 0, 0)))
150    }
151}
152
153impl FromStr for MtreeSchema {
154    type Err = Error;
155
156    /// Creates an [`MtreeSchema`] from string slice `s`.
157    ///
158    /// Relies on [`SchemaVersion::from_str`] to create a corresponding [`MtreeSchema`] from
159    /// `s`.
160    ///
161    /// # Errors
162    ///
163    /// Returns an error if
164    /// - no [`SchemaVersion`] can be created from `s`,
165    /// - or the conversion from [`SchemaVersion`] to [`MtreeSchema`] fails.
166    fn from_str(s: &str) -> Result<MtreeSchema, Self::Err> {
167        match SchemaVersion::from_str(s) {
168            Ok(version) => Self::try_from(version),
169            Err(_) => Err(Error::UnsupportedSchemaVersion(s.to_string())),
170        }
171    }
172}
173
174impl TryFrom<SchemaVersion> for MtreeSchema {
175    type Error = Error;
176
177    /// Converts a [`SchemaVersion`] to an [`MtreeSchema`].
178    ///
179    /// # Errors
180    ///
181    /// Returns an error if the [`SchemaVersion`]'s inner [`Version`] does not provide a major
182    /// version that corresponds to an [`MtreeSchema`] variant.
183    fn try_from(value: SchemaVersion) -> Result<Self, Self::Error> {
184        match value.inner().major {
185            1 => Ok(MtreeSchema::V1(value)),
186            2 => Ok(MtreeSchema::V2(value)),
187            _ => Err(Error::UnsupportedSchemaVersion(value.to_string())),
188        }
189    }
190}
191
192impl Display for MtreeSchema {
193    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
194        write!(
195            fmt,
196            "{}",
197            match self {
198                MtreeSchema::V1(version) | MtreeSchema::V2(version) => version.inner().major,
199            }
200        )
201    }
202}