freebsd_geom/
structs.rs

1//! This is the uncleaned result of XML deserialization.  You probably want the objects and methods
2//! in the `geom::graph` module instead.
3extern crate serde;
4
5//use serde::{de::Error, Deserialize, Deserializer};
6use serde::Deserialize;
7
8use crate::Error;
9
10/// A `Mesh` is the top-level structure representing a GEOM object graph.
11///
12/// The mesh contains objects from various classes, called "geoms."  `Geom`s represent things like
13/// disks, or disk partitions, or device nodes under `/dev` on FreeBSD systems.  They are related
14/// by references called "consumers" and "providers."
15#[derive(Debug, Deserialize, PartialEq)]
16pub struct Mesh {
17    #[serde(rename = "class", default)]
18    pub classes: Vec<Class>,
19}
20
21/// `Class` contains all of the objects ("geoms") and associated relationships ("consumers" and
22/// "providers") associated with the class.
23#[derive(Debug, Deserialize, PartialEq)]
24pub struct Class {
25    // Ideally, deserialize directly to u64.  However, neither of these works:
26    //#[serde(with = "SerHex::<CompactPfx>")]
27    //#[serde(borrow, deserialize_with = "from_hex")]
28    pub id: String, // uintptr_t
29    pub name: String,
30    #[serde(rename = "geom", default)]
31    pub geoms: Vec<Geom>,
32    // libgeom(3) thinks Classes have config sections, but I don't see any.
33}
34
35/// A `Geom` is the essential object in a GEOM graph.
36///
37/// It can represent a disk (under the "DISK" class), or partition (under "PART"), or `/dev` device
38/// node (under "DEV"), as well as several other classes.
39///
40/// A geom is related to other geoms in a directed graph.  `Consumer` edges indicate that this geom
41/// depends on a lower-level (lower "`rank`") geom.  `Provider` edges indicate that this geom
42/// exposes an object to a higher-level object.  For example, a PART geom might "consume" a DISK
43/// geom ("ada0") and "provide" logical partition objects ("ada0p1", "ada0p2", etc.).
44#[derive(Debug, Deserialize, PartialEq)]
45pub struct Geom {
46    pub id: String, // uintptr_t
47    #[serde(rename = "class")]
48    pub class_ref: ClassRef,
49    pub name: String,
50    pub rank: u64,
51    pub config: Option<GeomConfig>,
52    #[serde(rename = "consumer", default)]
53    pub consumers: Vec<Consumer>,
54    #[serde(rename = "provider", default)]
55    pub providers: Vec<Provider>,
56}
57
58/// A `ClassRef` is just a logical pointer to a `Class`.
59///
60/// `ClassRef::ref_` references the same namespace as `Class::id`.
61#[derive(Debug, Deserialize, PartialEq)]
62pub struct ClassRef {
63    #[serde(rename = "ref")]
64    pub ref_: String, // uintptr_t
65}
66
67/// A set of key-value metadata associated with a specific `Geom`.
68///
69/// The semantics and available values vary depending on the class.
70#[derive(Debug, Deserialize, PartialEq)]
71pub struct GeomConfig {
72    // PART
73    pub scheme: Option<String>,
74    pub entries: Option<u64>,
75    pub first: Option<u64>,
76    pub last: Option<u64>,
77    pub fwsectors: Option<u64>,
78    pub fwheads: Option<u64>,
79    pub state: Option<String>, // "OK"
80    pub modified: Option<bool>,
81}
82
83/// A pointer from one geom to a `Provider` of a lower-level geom.
84///
85/// In the logical directed graph, it is an out-edge.
86///
87/// It is associated with the `Geom` with `id` equal to `geom_ref.ref_`, and points to the
88/// `Provider` with `id` equal to `provider_ref.ref_`.
89#[derive(Debug, Deserialize, PartialEq)]
90pub struct Consumer {
91    pub id: String, // uintptr_t
92    #[serde(rename = "geom")]
93    pub geom_ref: GeomRef,
94    #[serde(rename = "provider")]
95    pub provider_ref: ProviderRef,
96    pub mode: String,
97}
98
99/// A pointer into a geom from the `Consumer` of a higher-level geom.
100///
101/// In the logical directed graph, it is an in-edge.
102///
103/// It is associated with the `Geom` with `id` equal to `geom_ref.ref_`.
104#[derive(Debug, Deserialize, PartialEq)]
105pub struct Provider {
106    pub id: String, // uintptr_t
107    #[serde(rename = "geom")]
108    pub geom_ref: GeomRef,
109    pub mode: String,
110    pub name: String,
111    pub mediasize: u64,
112    pub sectorsize: u64,
113    pub stripesize: u64,
114    pub stripeoffset: u64,
115    pub config: ProviderConfig,
116}
117
118// Ideally this would be some enum type based on the Class, but, ya know.  (serde(flatten) / enum
119// interaction doesn't seem flawless at this time.)
120/// A set of key-value metadata associated with a specific `Provider`.
121///
122/// The semantics and available values vary depending on the class.
123#[derive(Debug, Deserialize, PartialEq)]
124pub struct ProviderConfig {
125    // DISK
126    pub fwheads: Option<u64>,
127    pub fwsectors: Option<u64>,
128    pub rotationrate: Option<String>,
129    pub ident: Option<String>,
130    pub lunid: Option<String>,
131    pub descr: Option<String>,
132    // PART
133    pub start: Option<u64>,
134    pub end: Option<u64>,
135    pub index: Option<u64>,
136    #[serde(rename = "type")]
137    pub type_: Option<String>,
138    pub offset: Option<u64>,
139    pub length: Option<u64>,
140    pub label: Option<String>,
141    pub rawtype: Option<String>,
142    pub rawuuid: Option<String>,
143    pub efimedia: Option<String>,
144    // LABEL
145    // index, length, offset shared with PART above
146    pub seclength: Option<u64>,
147    pub secoffset: Option<u64>,
148}
149
150/// A `GeomRef` is just a logical pointer to a `Geom`.
151///
152/// `GeomRef::ref_` references the same namespace as `Geom::id`.
153#[derive(Debug, Deserialize, PartialEq)]
154pub struct GeomRef {
155    #[serde(rename = "ref")]
156    pub ref_: String, // uintptr_t
157}
158
159/// A `ProviderRef` is just a logical pointer to a `Provider`.
160///
161/// `ProviderRef::ref_` references the same namespace as `Provider::id`.
162#[derive(Debug, Deserialize, PartialEq)]
163pub struct ProviderRef {
164    #[serde(rename = "ref")]
165    pub ref_: String, // uintptr_t
166}
167
168/// Parse a GEOM XML string configuration into a geom::raw::Mesh structure.
169///
170/// # Arguments
171///
172/// * `xml` - A string slice of the contents of the `kern.geom.confxml` `sysctl` node from a
173///   FreeBSD system.
174///
175/// # Examples
176///
177/// ```
178/// use freebsd_geom as geom;
179///
180/// let mesh = geom::raw::parse_xml(r#"<mesh> ... </mesh>"#).unwrap();
181/// println!("The mesh has {} classes.", mesh.classes.len());
182/// ```
183pub fn parse_xml(xml: &str) -> Result<Mesh, Error> {
184    return Ok(quick_xml::de::from_str::<Mesh>(xml)?);
185}
186
187/// Returns a structure representing the raw GEOM mesh on the running system.
188///
189/// # Examples
190///
191/// ```
192/// use freebsd_geom as geom;
193/// use std::collections::BTreeMap;
194///
195/// fn myfoo() -> Result<(), geom::Error> {
196///     let mesh = geom::raw::get_mesh()?;
197///
198///     let mut count = BTreeMap::new();
199///     for g_class in &mesh.classes {
200///         count.insert(&g_class.name, g_class.geoms.len());
201///     }
202///     for (class_name, count) in &count {
203///         println!("class {}: {} geoms", class_name, count);
204///     }
205///     Ok(())
206/// }
207/// ```
208#[cfg(target_os = "freebsd")]
209pub fn get_mesh() -> Result<Mesh, Error> {
210    let xml = crate::get_confxml()?;
211    return Ok(parse_xml(&xml)?);
212}
213
214#[cfg(test)]
215mod tests {
216    use crate::structs;
217
218    #[test]
219    fn xml_mesh_basic() {
220        let xml = "<mesh></mesh>";
221        quick_xml::de::from_str::<structs::Mesh>(xml).unwrap();
222    }
223
224    #[test]
225    fn xml_class_basic() {
226        let xml = "<class id=\"0xffffffff81234567\"><name>FD</name></class>";
227        let cls = quick_xml::de::from_str::<structs::Class>(xml).unwrap();
228        assert_eq!(cls.id, "0xffffffff81234567");
229        assert_eq!(cls.name, "FD");
230    }
231
232    #[test]
233    fn xml_references() {
234        let xml = "<class ref=\"0x123\"/>";
235        let cls = quick_xml::de::from_str::<structs::ClassRef>(xml).unwrap();
236        assert_eq!(cls.ref_, "0x123");
237
238        let xml = "<provider ref=\"0x123\"/>";
239        let p = quick_xml::de::from_str::<structs::ProviderRef>(xml).unwrap();
240        assert_eq!(p.ref_, "0x123");
241
242        let xml = "<geom ref=\"0x123\"/>";
243        let p = quick_xml::de::from_str::<structs::GeomRef>(xml).unwrap();
244        assert_eq!(p.ref_, "0x123");
245    }
246
247    #[test]
248    fn xml_geom_config() {
249        let xml = r#"<config><scheme>GPT</scheme></config>"#;
250        let p = quick_xml::de::from_str::<structs::GeomConfig>(xml).unwrap();
251        assert_eq!(p.scheme.unwrap(), "GPT");
252    }
253
254    #[test]
255    fn xml_consumer() {
256        let xml = r#"<consumer id="0x123">
257                        <geom ref="0x456"/>
258                        <provider ref="0x789"/>
259                        <mode>r0w0e0</mode>
260                    </consumer>"#;
261        let p = quick_xml::de::from_str::<structs::Consumer>(xml).unwrap();
262        assert_eq!(
263            p,
264            structs::Consumer {
265                id: "0x123".into(),
266                geom_ref: structs::GeomRef {
267                    ref_: "0x456".into()
268                },
269                provider_ref: structs::ProviderRef {
270                    ref_: "0x789".into()
271                },
272                mode: "r0w0e0".into(),
273            }
274        );
275    }
276
277    #[test]
278    fn xml_provider_config() {
279        // DISK class
280        let xml = r#"<config>
281                        <fwheads>1</fwheads>
282                        <fwsectors>2</fwsectors>
283                        <rotationrate>0</rotationrate>
284                        <ident>S3Z</ident>
285                        <lunid>00123abcd</lunid>
286                        <descr>Samsung SSD</descr>
287                    </config>"#;
288        let p = quick_xml::de::from_str::<structs::ProviderConfig>(xml).unwrap();
289        assert_eq!(
290            p,
291            structs::ProviderConfig {
292                fwheads: Some(1),
293                fwsectors: Some(2),
294                rotationrate: Some("0".into()),
295                ident: Some("S3Z".into()),
296                lunid: Some("00123abcd".into()),
297                descr: Some("Samsung SSD".into()),
298                // PART fields
299                start: None,
300                end: None,
301                index: None,
302                type_: None,
303                offset: None,
304                length: None,
305                label: None,
306                rawtype: None,
307                rawuuid: None,
308                efimedia: None,
309                // LABEL fields
310                seclength: None,
311                secoffset: None,
312            }
313        );
314    }
315
316    #[test]
317    fn xml_provider() {
318        let xml = r#"<provider id="0x123">
319                        <geom ref="0x456"/>
320                        <mode>r1w1e3</mode>
321                        <name>ada0</name>
322                        <mediasize>10</mediasize>
323                        <sectorsize>2</sectorsize>
324                        <stripesize>0</stripesize>
325                        <stripeoffset>123</stripeoffset>
326                        <config>
327                            <fwheads>1</fwheads>
328                            <fwsectors>2</fwsectors>
329                            <rotationrate>0</rotationrate>
330                            <ident>S3Z</ident>
331                            <lunid>00123abcd</lunid>
332                            <descr>Samsung SSD</descr>
333                        </config>
334                    </provider>"#;
335        let p = quick_xml::de::from_str::<structs::Provider>(xml).unwrap();
336        assert_eq!(p.id, "0x123");
337        assert_eq!(
338            p.geom_ref,
339            structs::GeomRef {
340                ref_: "0x456".into()
341            }
342        );
343        assert_eq!(p.mode, "r1w1e3");
344        assert_eq!(p.name, "ada0");
345        assert_eq!(p.mediasize, 10);
346        assert_eq!(p.sectorsize, 2);
347        assert_eq!(p.stripesize, 0);
348        assert_eq!(p.stripeoffset, 123);
349    }
350
351    #[test]
352    fn xml_geom_basic() {
353        let xml = r#"<geom id="0x123">
354                        <class ref="0x456"/>
355                        <name>ada0</name>
356                        <rank>1</rank>
357                        <config>
358                        </config>
359                    </geom>"#;
360        let p = quick_xml::de::from_str::<structs::Geom>(xml).unwrap();
361        assert_eq!(
362            p,
363            structs::Geom {
364                id: "0x123".into(),
365                class_ref: structs::ClassRef {
366                    ref_: "0x456".into()
367                },
368                name: "ada0".into(),
369                rank: 1,
370                config: Some(structs::GeomConfig {
371                    scheme: None,
372                    entries: None,
373                    first: None,
374                    last: None,
375                    fwsectors: None,
376                    fwheads: None,
377                    state: None,
378                    modified: None,
379                }),
380                consumers: vec![],
381                providers: vec![],
382            }
383        );
384    }
385
386    #[test]
387    fn xml_full_sample() {
388        let xml = include_str!("test/fullsample.xml");
389        let p = quick_xml::de::from_str::<structs::Mesh>(xml).unwrap();
390
391        // Some arbitrarily chosen doc queries
392        assert_eq!(p.classes[0].name, "FD");
393        assert_eq!(p.classes[1].name, "RAID");
394
395        assert_eq!(p.classes[2].name, "DISK");
396        assert_eq!(p.classes[2].id, p.classes[2].geoms[0].class_ref.ref_);
397        assert_eq!(
398            p.classes[2].geoms[0].id,
399            p.classes[2].geoms[0].providers[0].geom_ref.ref_
400        );
401        assert_eq!(p.classes[2].geoms[0].providers[0].mediasize, 1000204886016);
402        assert_eq!(
403            p.classes[2].geoms[0].providers[0]
404                .config
405                .lunid
406                .as_ref()
407                .unwrap(),
408            "YYYYYYYYYYYYYYYY"
409        );
410        assert_eq!(p.classes[2].geoms[1].name, "nvd1");
411
412        assert_eq!(p.classes[3].name, "DEV");
413        assert_eq!(p.classes[3].id, p.classes[3].geoms[0].class_ref.ref_);
414        assert_eq!(p.classes[3].geoms[1].name, "ada0p1");
415
416        // DEV consumer -> PART provider
417        assert_eq!(
418            p.classes[3].geoms[1].consumers[0].provider_ref.ref_,
419            p.classes[4].geoms[0].providers[0].id
420        );
421
422        assert_eq!(p.classes[4].name, "PART");
423        assert_eq!(p.classes[5].name, "LABEL");
424        assert_eq!(p.classes[6].name, "VFS");
425        assert_eq!(p.classes[7].name, "SWAP");
426        assert_eq!(p.classes[8].name, "Flashmap");
427        assert_eq!(p.classes[9].name, "MD");
428    }
429}