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}