1#![no_std]
24#![forbid(unsafe_code)]
25#![cfg_attr(docsrs, feature(doc_cfg))]
26#![deny(rustdoc::broken_intra_doc_links)]
27
28extern crate alloc;
29
30use alloc::string::{String, ToString};
31use alloc::vec::Vec;
32
33use embedded_io::{ErrorKind, Read, Seek, SeekFrom};
34use serde::Deserialize;
35use snafu::{OptionExt, Snafu, ensure};
36
37use crate::header::{MetadataAreaHeader, PhysicalVolumeHeader, PhysicalVolumeLabelHeader};
38use crate::metadata::{MetadataRoot, deserialize::MetadataElements};
39
40mod force_de_typed_map;
41mod header;
42mod lv;
43pub mod metadata;
44mod owned;
45
46pub use lv::*;
47pub use owned::OwnedLvReader;
48
49#[cfg(feature = "__fuzzing")]
53pub mod __fuzzing {
54 pub use crate::force_de_typed_map::ForceDeTypedMap;
55 pub use crate::header::{MetadataAreaHeader, PhysicalVolumeHeader, PhysicalVolumeLabelHeader};
56 pub use crate::metadata::MetadataRoot;
57 pub use crate::metadata::deserialize::MetadataElements;
58}
59
60#[derive(Debug, Snafu)]
68pub enum Error {
69 #[snafu(display("I/O error: {kind:?}"))]
71 Io { kind: ErrorKind },
72
73 #[snafu(display("unexpected end of PV input during read_exact"))]
77 UnexpectedEof,
78
79 #[snafu(display("PV label header has wrong magic"))]
81 WrongMagic,
82
83 #[snafu(display("parse error: {reason}"))]
85 Parse { reason: String },
86
87 #[snafu(display("metadata declares multiple VGs (single-VG-per-PV only)"))]
90 MultipleVGs,
91
92 #[snafu(display("PV metadata does not reference this PV's own UUID"))]
95 PVDoesntContainItself,
96
97 #[snafu(display("metadata deserialize error: {reason}"))]
99 Serde { reason: String },
100
101 #[snafu(display("PV header is missing a metadata area descriptor"))]
103 MissingMetadata,
104
105 #[snafu(display("metadata text was not valid UTF-8"))]
107 MetadataNotUtf8,
108}
109
110#[expect(
112 clippy::needless_pass_by_value,
113 reason = "used as a `map_err` function argument, which must take E by value"
114)]
115fn io_err<E: embedded_io::Error>(e: E) -> Error {
116 Error::Io { kind: e.kind() }
117}
118
119fn read_exact_err<E: embedded_io::Error>(e: embedded_io::ReadExactError<E>) -> Error {
121 match e {
122 embedded_io::ReadExactError::UnexpectedEof => Error::UnexpectedEof,
123 embedded_io::ReadExactError::Other(inner) => Error::Io { kind: inner.kind() },
124 }
125}
126
127pub struct Lvm2 {
130 pvh: PhysicalVolumeHeader,
131 pv_name: String,
132 vg_name: String,
133 vg_config: MetadataRoot,
134}
135
136impl Lvm2 {
137 pub fn open<T: Read + Seek>(mut reader: T) -> Result<Self, Error> {
142 reader.seek(SeekFrom::Start(512)).map_err(io_err)?;
144
145 let mut buf = [0u8; 512];
146 reader.read_exact(&mut buf).map_err(read_exact_err)?;
147 tracing::trace!(?buf);
148
149 let (_, vhl) = PhysicalVolumeLabelHeader::parse(&buf).map_err(|e| Error::Parse {
150 reason: e.to_string(),
151 })?;
152 tracing::trace!(?vhl);
153 let (_, pvh) =
154 PhysicalVolumeHeader::parse(&buf[(vhl.data_offset as usize)..]).map_err(|e| {
155 Error::Parse {
156 reason: e.to_string(),
157 }
158 })?;
159 tracing::trace!(?pvh);
160
161 let metadata_descriptor = pvh
162 .metadata_descriptors
163 .first()
164 .context(MissingMetadataSnafu)?;
165
166 reader
167 .seek(SeekFrom::Start(metadata_descriptor.offset))
168 .map_err(io_err)?;
169 reader.read_exact(&mut buf).map_err(read_exact_err)?;
170 let (_, mah) = MetadataAreaHeader::parse(&buf).map_err(|e| Error::Parse {
171 reason: e.to_string(),
172 })?;
173 tracing::trace!(?mah);
174
175 let mut metadata_bytes: Vec<u8> = Vec::new();
179 for locdesc in &mah.location_descriptors {
180 reader
181 .seek(SeekFrom::Start(
182 metadata_descriptor.offset + locdesc.data_area_offset,
183 ))
184 .map_err(io_err)?;
185 let len = usize::try_from(locdesc.data_area_size).map_err(|_| Error::Parse {
186 reason: "metadata area size overflows usize".to_string(),
187 })?;
188 let start = metadata_bytes.len();
189 metadata_bytes.resize(start + len, 0);
190 reader
191 .read_exact(&mut metadata_bytes[start..])
192 .map_err(read_exact_err)?;
193 }
194 let metadata = core::str::from_utf8(&metadata_bytes).map_err(|_| Error::MetadataNotUtf8)?;
195 tracing::debug!(%metadata);
196
197 let (trailing_garbage, metadata) =
198 MetadataElements::parse(metadata).map_err(|e| Error::Parse {
199 reason: e.to_string(),
200 })?;
201 tracing::debug!(?trailing_garbage, ?metadata);
202
203 let meta_root =
204 force_de_typed_map::ForceDeTypedMap::<String, MetadataRoot>::deserialize(&metadata)
205 .map_err(|e| Error::Serde {
206 reason: e.to_string(),
207 })?;
208 tracing::debug!(?meta_root);
209
210 ensure!(meta_root.0.len() == 1, MultipleVGsSnafu);
211 #[expect(
212 clippy::unwrap_used,
213 reason = "the ensure! above guarantees exactly one entry"
214 )]
215 let (vg_name, vg_config) = meta_root.0.into_iter().next().unwrap();
216
217 let pv_name = vg_config
218 .physical_volumes
219 .iter()
220 .find(|(_, v)| v.id.replace('-', "") == pvh.pv_ident)
221 .context(PVDoesntContainItselfSnafu)?
222 .0
223 .clone();
224
225 Ok(Self {
226 pvh,
227 pv_name,
228 vg_name,
229 vg_config,
230 })
231 }
232
233 pub fn pv_id(&self) -> &str {
234 &self.vg_config.physical_volumes[&self.pv_name].id
235 }
236
237 pub fn pv_name(&self) -> &str {
238 &self.pv_name
239 }
240
241 pub fn vg_name(&self) -> &str {
242 &self.vg_name
243 }
244
245 pub fn vg_id(&self) -> &str {
246 &self.vg_config.id
247 }
248
249 pub fn lvs(&self) -> impl Iterator<Item = LV<'_>> {
250 self.vg_config
251 .logical_volumes
252 .iter()
253 .map(|(name, desc)| LV { name, desc })
254 }
255
256 pub fn open_lv_by_name<'a, T: Read + Seek>(
257 &'a self,
258 name: &str,
259 reader: T,
260 ) -> Option<OpenLV<'a, T>> {
261 self.vg_config
262 .logical_volumes
263 .get_key_value(name)
264 .map(move |(name, desc)| self.open_lv(LV { name, desc }, reader))
265 }
266
267 pub fn open_lv_by_id<'a, T: Read + Seek>(
268 &'a self,
269 id: &str,
270 reader: T,
271 ) -> Option<OpenLV<'a, T>> {
272 self.lvs()
273 .find(|lv| lv.id() == id)
274 .map(move |lv| self.open_lv(lv, reader))
275 }
276
277 pub fn open_lv<'a, T: Read + Seek>(&'a self, lv: LV<'a>, reader: T) -> OpenLV<'a, T> {
278 OpenLV {
279 lv,
280 lvm: self,
281 reader,
282 position: 0,
283 current_segment_end: 0,
284 }
285 }
286
287 pub fn extent_size(&self) -> u64 {
288 self.vg_config.extent_size * 512
289 }
290
291 pub(crate) fn pv_header(&self) -> &PhysicalVolumeHeader {
294 &self.pvh
295 }
296
297 pub(crate) fn vg_config_lookup(
300 &self,
301 name: &str,
302 ) -> Option<(&String, &crate::metadata::LVDesc)> {
303 self.vg_config.logical_volumes.get_key_value(name)
304 }
305}