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