Skip to main content

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