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}