1#![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}