device_types/
lib.rs

1// Copyright (c) 2020 DDN. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5#![allow(clippy::large_enum_variant)]
6
7pub mod devices;
8pub mod udev;
9pub mod uevent;
10
11use std::{
12    cmp::Ordering,
13    collections::BTreeSet,
14    hash::{Hash, Hasher},
15    path::{Path, PathBuf},
16};
17
18#[derive(Debug, Clone, Eq, serde::Serialize, serde::Deserialize)]
19#[serde(transparent)]
20pub struct DevicePath(pub PathBuf);
21
22impl<S: Into<PathBuf>> From<S> for DevicePath {
23    fn from(s: S) -> DevicePath {
24        DevicePath(s.into())
25    }
26}
27
28impl<'a> From<&'a DevicePath> for &'a Path {
29    fn from(s: &'a DevicePath) -> &'a Path {
30        Path::new(&s.0)
31    }
32}
33
34fn find_sort_slot(DevicePath(p): &DevicePath) -> usize {
35    let o = &[
36        Box::new(|p: &PathBuf| p.starts_with("/dev/mapper/")) as Box<dyn Fn(&PathBuf) -> bool>,
37        Box::new(|p| p.starts_with("/dev/disk/by-id/")),
38        Box::new(|p| p.starts_with("/dev/disk/by-path/")),
39        Box::new(|p| p.starts_with("/dev/")),
40        Box::new(|_| true),
41    ]
42    .iter()
43    .position(|f| f(&p))
44    .unwrap();
45
46    *o
47}
48
49pub fn get_vdev_paths(vdev: &libzfs_types::VDev) -> BTreeSet<DevicePath> {
50    match vdev {
51        libzfs_types::VDev::Disk { dev_id, path, .. } => {
52            let p = dev_id
53                .as_ref()
54                .map(|x| format!("/dev/disk/by-id/{}", x))
55                .map(std::convert::Into::into)
56                .or_else(|| {
57                    tracing::warn!(
58                        "VDev::Disk.dev_id not found, using VDev::Disk.path {:?}",
59                        path
60                    );
61
62                    Some(path.clone())
63                })
64                .map(DevicePath);
65
66            let mut b = BTreeSet::new();
67
68            if let Some(x) = p {
69                b.insert(x);
70            }
71
72            b
73        }
74        libzfs_types::VDev::File { .. } => BTreeSet::new(),
75        libzfs_types::VDev::Mirror { children, .. }
76        | libzfs_types::VDev::RaidZ { children, .. }
77        | libzfs_types::VDev::Replacing { children, .. } => {
78            children.iter().flat_map(get_vdev_paths).collect()
79        }
80        libzfs_types::VDev::Root {
81            children,
82            spares,
83            cache,
84            ..
85        } => vec![children, spares, cache]
86            .into_iter()
87            .flatten()
88            .flat_map(get_vdev_paths)
89            .collect(),
90    }
91}
92
93impl Ord for DevicePath {
94    fn cmp(&self, other: &DevicePath) -> Ordering {
95        let a_slot = find_sort_slot(self);
96        let b_slot = find_sort_slot(other);
97
98        match a_slot.cmp(&b_slot) {
99            Ordering::Greater => Ordering::Greater,
100            Ordering::Less => Ordering::Less,
101            Ordering::Equal => self.0.partial_cmp(&other.0).unwrap(),
102        }
103    }
104}
105
106impl PartialOrd for DevicePath {
107    fn partial_cmp(&self, other: &DevicePath) -> Option<Ordering> {
108        Some(self.cmp(other))
109    }
110}
111
112impl PartialEq for DevicePath {
113    fn eq(&self, other: &DevicePath) -> bool {
114        self.0 == other.0
115    }
116}
117
118impl Hash for DevicePath {
119    fn hash<H: Hasher>(&self, h: &mut H) {
120        self.0.as_path().hash(h)
121    }
122}
123
124pub mod message {
125    #[derive(Debug, serde::Serialize, serde::Deserialize)]
126    pub enum Message {
127        Data(String),
128        Heartbeat,
129    }
130}
131
132pub mod state {
133    use crate::{mount, uevent};
134    use im::{HashMap, HashSet};
135    use std::path::PathBuf;
136
137    pub type UEvents = HashMap<PathBuf, uevent::UEvent>;
138
139    pub type ZedEvents = HashMap<u64, libzfs_types::Pool>;
140
141    #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
142    pub struct State {
143        pub uevents: UEvents,
144        pub zed_events: ZedEvents,
145        pub local_mounts: HashSet<mount::Mount>,
146    }
147
148    impl State {
149        pub fn new() -> Self {
150            State {
151                uevents: HashMap::new(),
152                zed_events: HashMap::new(),
153                local_mounts: HashSet::new(),
154            }
155        }
156    }
157}
158
159pub mod mount {
160    use crate::DevicePath;
161    use std::path::PathBuf;
162
163    #[derive(
164        Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
165    )]
166    #[serde(transparent)]
167    pub struct MountPoint(pub PathBuf);
168
169    #[derive(
170        Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
171    )]
172    #[serde(transparent)]
173    pub struct FsType(pub String);
174
175    #[derive(
176        Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
177    )]
178    pub struct MountOpts(pub String);
179
180    #[derive(
181        Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, Clone,
182    )]
183    pub struct Mount {
184        pub source: DevicePath,
185        pub target: MountPoint,
186        pub fs_type: FsType,
187        pub opts: MountOpts,
188    }
189
190    impl Mount {
191        pub fn new(
192            target: MountPoint,
193            source: DevicePath,
194            fs_type: FsType,
195            opts: MountOpts,
196        ) -> Self {
197            Mount {
198                target,
199                source,
200                fs_type,
201                opts,
202            }
203        }
204    }
205
206    #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
207    pub enum MountCommand {
208        AddMount(MountPoint, DevicePath, FsType, MountOpts),
209        RemoveMount(MountPoint, DevicePath, FsType, MountOpts),
210        ReplaceMount(MountPoint, DevicePath, FsType, MountOpts, MountOpts),
211        MoveMount(MountPoint, DevicePath, FsType, MountOpts, MountPoint),
212    }
213}
214
215pub mod zed {
216
217    #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
218    pub enum PoolCommand {
219        AddPools(Vec<libzfs_types::Pool>),
220        AddPool(libzfs_types::Pool),
221        UpdatePool(libzfs_types::Pool),
222        RemovePool(zpool::Guid),
223        AddDataset(zpool::Guid, libzfs_types::Dataset),
224        RemoveDataset(zpool::Guid, zfs::Name),
225        SetZpoolProp(zpool::Guid, prop::Key, prop::Value),
226        SetZfsProp(zpool::Guid, zfs::Name, prop::Key, prop::Value),
227    }
228
229    pub mod zpool {
230        #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
231        pub struct Name(pub String);
232
233        #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
234        pub struct Guid(pub String);
235
236        impl From<u64> for Guid {
237            fn from(x: u64) -> Self {
238                Guid(format!("{:#018X}", x))
239            }
240        }
241
242        impl From<Guid> for Result<u64, std::num::ParseIntError> {
243            fn from(Guid(x): Guid) -> Self {
244                let without_prefix = x.trim_start_matches("0x");
245                u64::from_str_radix(without_prefix, 16)
246            }
247        }
248
249        #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
250        pub struct State(pub String);
251
252        impl From<State> for String {
253            fn from(State(x): State) -> Self {
254                x
255            }
256        }
257    }
258
259    pub mod zfs {
260        #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
261        pub struct Name(pub String);
262    }
263
264    pub mod prop {
265        #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
266        pub struct Key(pub String);
267
268        #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
269        pub struct Value(pub String);
270    }
271
272    #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
273    pub enum ZedCommand {
274        Init,
275        CreateZpool(zpool::Name, zpool::Guid, zpool::State),
276        ImportZpool(zpool::Name, zpool::Guid, zpool::State),
277        ExportZpool(zpool::Guid, zpool::State),
278        DestroyZpool(zpool::Guid),
279        CreateZfs(zpool::Guid, zfs::Name),
280        DestroyZfs(zpool::Guid, zfs::Name),
281        SetZpoolProp(zpool::Guid, prop::Key, prop::Value),
282        SetZfsProp(zpool::Guid, zfs::Name, prop::Key, prop::Value),
283        AddVdev(zpool::Name, zpool::Guid),
284    }
285}
286
287#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
288pub enum Command {
289    Stream,
290    GetMounts,
291    PoolCommand(zed::PoolCommand),
292    UdevCommand(udev::UdevCommand),
293    MountCommand(mount::MountCommand),
294}
295
296#[cfg(test)]
297mod tests {
298    use super::{
299        mount, {Command, DevicePath},
300    };
301    use im::{ordset, OrdSet};
302    use insta::assert_debug_snapshot;
303
304    #[test]
305    fn test_device_path_ordering() {
306        let xs: OrdSet<DevicePath> = ordset![
307            "/dev/disk/by-id/dm-uuid-part1-mpath-3600140550e41a841db244a992c31e7df".into(),
308            "/dev/mapper/mpathd1".into(),
309            "/dev/disk/by-uuid/b4550256-cf48-4013-8363-bfee5f52da12".into(),
310            "/dev/disk/by-partuuid/d643e32f-b6b9-4863-af8f-8950376e28da".into(),
311            "/dev/dm-20".into(),
312            "/dev/disk/by-id/dm-name-mpathd1".into()
313        ];
314
315        assert_debug_snapshot!(xs);
316    }
317
318    #[test]
319    fn test_mount_deserialize() {
320        let s = "{\"MountCommand\":{\"AddMount\":[\"swap\",\"/dev/mapper/VolGroup00-LogVol01\",\"swap\",\"defaults\"]}}";
321
322        let result = serde_json::from_str::<Command>(s).unwrap();
323
324        assert_eq!(
325            result,
326            Command::MountCommand(mount::MountCommand::AddMount(
327                mount::MountPoint("swap".into()),
328                "/dev/mapper/VolGroup00-LogVol01".into(),
329                mount::FsType("swap".to_string()),
330                mount::MountOpts("defaults".to_string())
331            ))
332        )
333    }
334}