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}