1use std::{
8    collections::{hash_map, HashMap, HashSet},
9    fmt,
10    hash::{Hash, Hasher},
11    path::Path,
12};
13
14use either::Either;
15use serde_json::Value;
16
17use crate::{
18    engine::{
19        shared::{gather_encryption_info, gather_pool_name},
20        strat_engine::{
21            backstore::blockdev::{v1, v2},
22            liminal::{
23                identify::{DeviceInfo, LuksInfo, StratisDevInfo, StratisInfo},
24                setup::{get_feature_set, get_name},
25            },
26            metadata::{StratisIdentifiers, BDA},
27        },
28        types::{
29            DevUuid, EncryptionInfo, Features, LockedPoolInfo, MaybeInconsistent, Name, PoolDevice,
30            PoolEncryptionInfo, PoolUuid, StoppedPoolInfo, StratSigblockVersion,
31        },
32    },
33    stratis::{StratisError, StratisResult},
34};
35
36#[derive(Debug, Eq, Hash, PartialEq)]
38pub struct LLuksInfo {
39    pub dev_info: StratisDevInfo,
40    pub identifiers: StratisIdentifiers,
41    pub encryption_info: EncryptionInfo,
42    pub pool_name: Option<Name>,
43}
44
45impl fmt::Display for LLuksInfo {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(
48            f,
49            "{}, {}, {}",
50            self.dev_info, self.identifiers, self.encryption_info,
51        )?;
52        if let Some(ref pn) = self.pool_name {
53            write!(f, ", {pn}")
54        } else {
55            Ok(())
56        }
57    }
58}
59
60impl From<LuksInfo> for LLuksInfo {
61    fn from(info: LuksInfo) -> LLuksInfo {
62        LLuksInfo {
63            dev_info: info.dev_info,
64            identifiers: info.identifiers,
65            encryption_info: info.encryption_info,
66            pool_name: info.pool_name,
67        }
68    }
69}
70
71impl Into<Value> for &LLuksInfo {
72    fn into(self) -> Value {
75        let mut json = <&StratisDevInfo as Into<Value>>::into(&self.dev_info);
76        let map = json
77            .as_object_mut()
78            .expect("StratisInfo conversion returns a JSON object");
79        map.extend(
80            if let Value::Object(id_map) =
81                <&StratisIdentifiers as Into<Value>>::into(&self.identifiers)
82            {
83                id_map.into_iter()
84            } else {
85                unreachable!("StratisIdentifiers conversion returns a JSON object");
86            },
87        );
88        map.extend(
89            if let Value::Object(enc_map) =
90                <&EncryptionInfo as Into<Value>>::into(&self.encryption_info)
91            {
92                enc_map.into_iter()
93            } else {
94                unreachable!("EncryptionInfo conversion returns a JSON object");
95            },
96        );
97        if let Some(ref n) = self.pool_name {
98            map.insert("pool_name".to_string(), Value::from(n.to_string()));
99        }
100        json
101    }
102}
103
104pub fn stratis_infos_ref(
106    infos: &HashMap<DevUuid, Box<LStratisInfo>>,
107) -> HashMap<DevUuid, &LStratisInfo> {
108    infos
109        .iter()
110        .map(|(dev_uuid, info)| (*dev_uuid, info.as_ref()))
111        .collect::<HashMap<_, _>>()
112}
113
114pub fn split_stratis_infos(
117    infos: HashMap<DevUuid, Box<LStratisInfo>>,
118) -> (
119    HashMap<DevUuid, Box<LStratisDevInfo>>,
120    HashMap<DevUuid, BDA>,
121) {
122    infos.into_iter().fold(
123        (HashMap::new(), HashMap::new()),
124        |(mut new_infos, mut bdas), (dev_uuid, info)| {
125            new_infos.insert(
126                dev_uuid,
127                Box::new(LStratisDevInfo {
128                    dev_info: info.dev_info,
129                    luks: info.luks,
130                }),
131            );
132            bdas.insert(dev_uuid, info.bda);
133            (new_infos, bdas)
134        },
135    )
136}
137
138pub fn reconstruct_stratis_infos(
142    mut infos: HashMap<DevUuid, Box<LStratisDevInfo>>,
143    mut bdas: HashMap<DevUuid, BDA>,
144) -> DeviceSet {
145    let uuids = infos.keys().copied().collect::<HashSet<_>>();
146    assert_eq!(uuids, bdas.keys().copied().collect::<HashSet<_>>());
147
148    uuids
149        .into_iter()
150        .map(|uuid| {
151            let info = infos.remove(&uuid).expect("infos.keys() == bdas.keys()");
152            let bda = bdas.remove(&uuid).expect("infos.keys() == bdas.keys()");
153            (
154                uuid,
155                LInfo::Stratis(Box::new(LStratisInfo {
156                    dev_info: info.dev_info,
157                    luks: info.luks,
158                    bda,
159                })),
160            )
161        })
162        .collect::<DeviceSet>()
163}
164
165#[derive(Debug, PartialEq, Eq, Hash)]
167pub struct LStratisDevInfo {
168    pub dev_info: StratisDevInfo,
169    pub luks: Option<LLuksInfo>,
173}
174
175impl From<LStratisInfo> for (LStratisDevInfo, BDA) {
176    fn from(info: LStratisInfo) -> Self {
177        (
178            LStratisDevInfo {
179                dev_info: info.dev_info,
180                luks: info.luks,
181            },
182            info.bda,
183        )
184    }
185}
186
187#[derive(Debug)]
189pub struct LStratisInfo {
190    pub dev_info: StratisDevInfo,
191    pub bda: BDA,
192    pub luks: Option<LLuksInfo>,
196}
197
198impl PartialEq for LStratisInfo {
199    fn eq(&self, rhs: &Self) -> bool {
200        self.bda.identifiers() == rhs.bda.identifiers() && self.dev_info == rhs.dev_info
201    }
202}
203
204impl Eq for LStratisInfo {}
205
206impl Hash for LStratisInfo {
207    fn hash<H>(&self, hasher: &mut H)
208    where
209        H: Hasher,
210    {
211        self.bda.identifiers().hash(hasher);
212        self.dev_info.hash(hasher);
213    }
214}
215
216impl fmt::Display for LStratisInfo {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        if let Some(info) = &self.luks {
219            write!(
220                f,
221                "logical device with {}, {} and physical device with {}",
222                self.bda.identifiers(),
223                self.dev_info,
224                info
225            )
226        } else {
227            write!(f, "{}, {}", self.bda.identifiers(), self.dev_info)
228        }
229    }
230}
231
232impl From<StratisInfo> for LStratisInfo {
233    fn from(info: StratisInfo) -> LStratisInfo {
234        LStratisInfo {
235            dev_info: info.dev_info,
236            bda: info.bda,
237            luks: None,
238        }
239    }
240}
241
242impl Into<Value> for &LStratisInfo {
243    fn into(self) -> Value {
246        let mut json = self
247            .luks
248            .as_ref()
249            .map(|luks| json!({ "luks": <&LLuksInfo as Into<Value>>::into(luks) }))
250            .unwrap_or_else(|| json!({}));
251        if let Value::Object(ref mut map) = json {
252            map.extend(
253                if let Value::Object(map) = <&StratisDevInfo as Into<Value>>::into(&self.dev_info) {
254                    map.into_iter()
255                } else {
256                    unreachable!("StratisInfo conversion returns a JSON object");
257                },
258            );
259            map.extend(
260                if let Value::Object(map) =
261                    <&StratisIdentifiers as Into<Value>>::into(&self.bda.identifiers())
262                {
263                    map.into_iter()
264                } else {
265                    unreachable!("StratisInfo conversion returns a JSON object");
266                },
267            );
268        } else {
269            unreachable!("json!() always creates a JSON object");
270        };
271        json
272    }
273}
274
275impl LStratisInfo {
276    #[allow(dead_code)]
277    fn invariant(&self) {
278        assert!(match &self.luks {
279            None => true,
280            Some(luks) =>
281                luks.identifiers == self.bda.identifiers()
282                    && luks.dev_info.devnode != self.dev_info.devnode
283                    && luks.dev_info.device_number != self.dev_info.device_number,
284        });
285    }
286}
287
288#[derive(Debug, Eq, Hash, PartialEq)]
291pub enum LInfo {
292    Stratis(Box<LStratisInfo>),
294    Luks(LLuksInfo),
296}
297
298impl fmt::Display for LInfo {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        match self {
301            LInfo::Stratis(info) => write!(f, "Stratis device with {info}"),
302            LInfo::Luks(info) => write!(f, "LUKS device belonging to Stratis with {info}"),
303        }
304    }
305}
306
307impl From<DeviceInfo> for LInfo {
308    fn from(info: DeviceInfo) -> LInfo {
309        match info {
310            DeviceInfo::Luks(info) => LInfo::Luks(info.into()),
311            DeviceInfo::Stratis(info) => LInfo::Stratis(Box::new(info.into())),
312        }
313    }
314}
315
316impl Into<Value> for &LInfo {
317    fn into(self) -> Value {
320        match self {
321            LInfo::Stratis(info) => info.as_ref().into(),
322            LInfo::Luks(info) => info.into(),
323        }
324    }
325}
326
327impl LInfo {
328    pub fn stratis_identifiers(&self) -> StratisIdentifiers {
329        match self {
330            LInfo::Luks(info) => info.identifiers,
331            LInfo::Stratis(info) => info.bda.identifiers(),
332        }
333    }
334
335    fn encryption_info(&self) -> Option<&EncryptionInfo> {
336        match self {
337            LInfo::Luks(info) => Some(&info.encryption_info),
338            LInfo::Stratis(info) => info.luks.as_ref().map(|i| &i.encryption_info),
339        }
340    }
341
342    pub fn is_encrypted(&self) -> bool {
345        self.encryption_info().is_some()
346    }
347
348    pub fn is_closed(&self) -> bool {
351        match self {
352            LInfo::Luks(_) => true,
353            LInfo::Stratis(_) => false,
354        }
355    }
356
357    fn update_on_remove(
359        info: LInfo,
360        path: &Path,
361        pool_uuid: PoolUuid,
362        dev_uuid: DevUuid,
363    ) -> Option<LInfo> {
364        match info {
365            LInfo::Luks(linfo) => {
366                if linfo.dev_info.devnode == path {
367                    None
368                } else {
369                    warn!("Device with pool UUID {pool_uuid}, device UUID {dev_uuid} appears to have been removed but the path did not match the known Stratis device with these identifiers");
370                    Some(LInfo::Luks(linfo))
371                }
372            }
373            LInfo::Stratis(sinfo) => {
374                if Some(path) == sinfo.luks.as_ref().map(|i| i.dev_info.devnode.as_path()) {
375                    info!("Encrypted backing device with pool UUID {pool_uuid}, device UUID {dev_uuid} is no longer available; removing activated devicemapper device as well");
376                    None
377                } else if path == sinfo.dev_info.devnode {
378                    if let Some(l) = sinfo.luks {
379                        info!("Encrypted Stratis device with pool UUID {pool_uuid}, device UUID {dev_uuid} is no longer available; marking encrypted backing device as closed");
380                        Some(LInfo::Luks(l))
381                    } else {
382                        info!("Stratis device with pool UUID {pool_uuid}, device UUID {dev_uuid} is no longer available");
383                        None
384                    }
385                } else {
386                    warn!("Device with pool UUID {pool_uuid}, device UUID {dev_uuid} appears to have been removed but the path did not match the known Stratis device with these identifiers");
387                    Some(LInfo::Stratis(sinfo))
388                }
389            }
390        }
391    }
392
393    fn update(info_1: LInfo, info_2: DeviceInfo) -> Either<LInfo, LInfo> {
397        fn luks_luks_compatible(info_1: &LLuksInfo, info_2: &LuksInfo) -> bool {
401            assert_eq!(info_1.identifiers, info_2.identifiers);
402            if info_1.dev_info.device_number == info_2.dev_info.device_number
403                && info_1.encryption_info == info_2.encryption_info
404            {
405                true
406            } else {
407                warn!(
408                    "LUKS info conflicts: {}, {} conflicts with {}, {}",
409                    info_1.dev_info.device_number,
410                    info_1.encryption_info,
411                    info_2.dev_info.device_number,
412                    info_2.encryption_info
413                );
414                false
415            }
416        }
417
418        fn stratis_stratis_compatible(info_1: &LStratisInfo, info_2: &StratisInfo) -> bool {
422            assert_eq!(info_1.bda.identifiers(), info_2.bda.identifiers());
423            if info_1.dev_info.device_number == info_2.dev_info.device_number {
424                true
425            } else {
426                warn!(
427                    "Stratis info conflicts: {} conflicts with {}",
428                    info_1.dev_info.device_number, info_2.dev_info.device_number
429                );
430                false
431            }
432        }
433
434        match (info_1, info_2) {
435            (LInfo::Luks(luks_info), DeviceInfo::Stratis(strat_info)) => {
436                Either::Left(LInfo::Stratis(Box::new(LStratisInfo {
437                    dev_info: strat_info.dev_info,
438                    bda: strat_info.bda,
439                    luks: Some(luks_info),
440                })))
441            }
442            (LInfo::Stratis(strat_info), DeviceInfo::Luks(luks_info)) => {
443                if let Some(luks) = strat_info.luks.as_ref() {
444                    if !luks_luks_compatible(luks, &luks_info) {
445                        return Either::Right(LInfo::Stratis(strat_info));
446                    }
447                }
448                Either::Left(LInfo::Stratis(Box::new(LStratisInfo {
449                    dev_info: strat_info.dev_info,
450                    bda: strat_info.bda,
451                    luks: Some(LLuksInfo::from(luks_info)),
452                })))
453            }
454            (LInfo::Luks(luks_info_1), DeviceInfo::Luks(luks_info_2)) => {
455                if !luks_luks_compatible(&luks_info_1, &luks_info_2) {
456                    Either::Right(LInfo::Luks(luks_info_1))
457                } else {
458                    Either::Left(LInfo::Luks(LLuksInfo::from(luks_info_2)))
459                }
460            }
461            (LInfo::Stratis(strat_info_1), DeviceInfo::Stratis(strat_info_2)) => {
462                if !stratis_stratis_compatible(&strat_info_1, &strat_info_2) {
463                    Either::Right(LInfo::Stratis(strat_info_1))
464                } else {
465                    Either::Left(LInfo::Stratis(Box::new(LStratisInfo {
466                        dev_info: strat_info_2.dev_info,
467                        bda: strat_info_2.bda,
468                        luks: strat_info_1.luks,
469                    })))
470                }
471            }
472        }
473    }
474}
475
476pub struct Iter<'a> {
478    items: hash_map::Iter<'a, DevUuid, LInfo>,
479}
480
481impl<'a> Iterator for Iter<'a> {
482    type Item = (&'a DevUuid, &'a LInfo);
483
484    #[inline]
485    fn next(&mut self) -> Option<Self::Item> {
486        self.items.next()
487    }
488}
489
490#[derive(Debug, Eq, PartialEq)]
492pub struct DeviceSet {
493    internal: HashMap<DevUuid, LInfo>,
494}
495
496impl Default for DeviceSet {
497    fn default() -> DeviceSet {
498        DeviceSet::new()
499    }
500}
501
502impl FromIterator<(DevUuid, LInfo)> for DeviceSet {
503    fn from_iter<I>(i: I) -> Self
504    where
505        I: IntoIterator<Item = (DevUuid, LInfo)>,
506    {
507        DeviceSet {
508            internal: HashMap::from_iter(i),
509        }
510    }
511}
512
513impl IntoIterator for DeviceSet {
514    type Item = <HashMap<DevUuid, LInfo> as IntoIterator>::Item;
515    type IntoIter = <HashMap<DevUuid, LInfo> as IntoIterator>::IntoIter;
516
517    fn into_iter(self) -> Self::IntoIter {
518        self.internal.into_iter()
519    }
520}
521
522impl From<Vec<v1::StratBlockDev>> for DeviceSet {
523    fn from(vec: Vec<v1::StratBlockDev>) -> Self {
524        vec.into_iter()
525            .fold(DeviceSet::default(), |mut device_set, bd| {
526                for info in Vec::<DeviceInfo>::from(bd) {
527                    device_set.process_info_add(info);
528                }
529                device_set
530            })
531    }
532}
533
534impl From<Vec<v2::StratBlockDev>> for DeviceSet {
535    fn from(vec: Vec<v2::StratBlockDev>) -> Self {
536        vec.into_iter()
537            .fold(DeviceSet::default(), |mut device_set, bd| {
538                for info in Vec::<DeviceInfo>::from(bd) {
539                    device_set.process_info_add(info);
540                }
541                device_set
542            })
543    }
544}
545
546impl DeviceSet {
547    pub fn new() -> DeviceSet {
549        DeviceSet {
550            internal: HashMap::new(),
551        }
552    }
553
554    pub fn iter(&self) -> Iter<'_> {
556        Iter {
557            items: self.internal.iter(),
558        }
559    }
560
561    pub fn some_closed(&self) -> bool {
563        self.internal.iter().any(|(_, info)| info.is_closed())
564    }
565
566    fn as_opened_set(&self) -> Option<HashMap<DevUuid, &LStratisInfo>> {
569        if self.some_closed() {
570            None
571        } else {
572            Some(
573                self.internal
574                    .iter()
575                    .map(|(dev_uuid, info)| match info {
576                        LInfo::Luks(_) => unreachable!("!self.some_closed() is satisfied"),
577                        LInfo::Stratis(info) => (*dev_uuid, info.as_ref()),
578                    })
579                    .collect(),
580            )
581        }
582    }
583
584    pub fn into_opened_set(self) -> Either<HashMap<DevUuid, Box<LStratisInfo>>, Self> {
587        if self.some_closed() {
588            Either::Right(self)
589        } else {
590            Either::Left(
591                self.internal
592                    .into_iter()
593                    .map(|(dev_uuid, info)| match info {
594                        LInfo::Luks(_) => unreachable!("!self.some_closed() is satisfied"),
595                        LInfo::Stratis(info) => (dev_uuid, info),
596                    })
597                    .collect(),
598            )
599        }
600    }
601
602    pub fn into_bag(mut self) -> DeviceBag {
604        DeviceBag {
605            internal: self.internal.drain().map(|(_, info)| info).collect(),
606        }
607    }
608
609    pub fn encryption_info(&self) -> StratisResult<Option<PoolEncryptionInfo>> {
611        gather_encryption_info(
612            self.internal.len(),
613            self.internal.values().map(|info| info.encryption_info()),
614        )
615    }
616
617    pub fn pool_level_metadata_info(
620        &self,
621    ) -> StratisResult<(MaybeInconsistent<Option<Name>>, Option<Features>)> {
622        match self.as_opened_set() {
623            Some(set) => get_name(&set).map(MaybeInconsistent::No).and_then(|name| {
624                get_feature_set(&set).map(|feat| (name, feat.map(Features::from)))
625            }),
626            None => gather_pool_name(
627                self.internal.len(),
628                self.internal.values().map(|info| match info {
629                    LInfo::Stratis(s) => s.luks.as_ref().map(|l| l.pool_name.as_ref()),
630                    LInfo::Luks(l) => Some(l.pool_name.as_ref()),
631                }),
632            )
633            .map(|opt| {
634                (
635                    opt.expect("self.as_opened_set().is_some() if pool is unencrypted"),
636                    None,
637                )
638            }),
639        }
640    }
641
642    pub fn locked_pool_info(&self) -> Option<LockedPoolInfo> {
656        gather_encryption_info(
657            self.internal.len(),
658            self.internal.values().map(|info| info.encryption_info()),
659        )
660        .ok()
661        .and_then(|info| info)
662        .and_then(|info| {
663            self.internal
664                .iter()
665                .map(|(uuid, l)| {
666                    let devnode = match l {
667                        LInfo::Stratis(strat_info) => {
668                            strat_info.luks.as_ref().map(|l| l.dev_info.devnode.clone())
669                        }
670                        LInfo::Luks(luks_info) => Some(luks_info.dev_info.devnode.clone()),
671                    };
672                    devnode.map(|devnode| PoolDevice {
673                        devnode,
674                        uuid: *uuid,
675                    })
676                })
677                .try_fold(Vec::new(), |mut vec, dev_info| {
678                    dev_info.map(|d| {
679                        vec.push(d);
680                        vec
681                    })
682                })
683                .map(|d| LockedPoolInfo {
684                    info: info.clone(),
685                    devices: d,
686                })
687        })
688    }
689
690    pub fn stopped_pool_info(&self) -> Option<StoppedPoolInfo> {
698        gather_encryption_info(
699            self.internal.len(),
700            self.internal.values().map(|info| info.encryption_info()),
701        )
702        .ok()
703        .map(|info| {
704            let features = match self.pool_level_metadata_info() {
705                Ok((_, opt)) => opt,
706                Err(e) => {
707                    warn!("Failed to read metadata for pool: {e}");
708                    None
709                }
710            };
711            StoppedPoolInfo {
712                info,
713                devices: self
714                    .internal
715                    .iter()
716                    .map(|(uuid, l)| {
717                        let devnode = match l {
718                            LInfo::Stratis(strat_info) => strat_info
719                                .luks
720                                .as_ref()
721                                .map(|l| l.dev_info.devnode.clone())
722                                .unwrap_or_else(|| strat_info.dev_info.devnode.clone()),
723                            LInfo::Luks(luks_info) => luks_info.dev_info.devnode.clone(),
724                        };
725                        PoolDevice {
726                            devnode,
727                            uuid: *uuid,
728                        }
729                    })
730                    .collect::<Vec<_>>(),
731                metadata_version: self.metadata_version().ok(),
732                features,
733            }
734        })
735    }
736
737    pub fn process_info_remove(&mut self, path: &Path, pool_uuid: PoolUuid, dev_uuid: DevUuid) {
741        match self.internal.remove(&dev_uuid) {
742            Some(LInfo::Luks(linfo)) => {
743                if path == linfo.dev_info.devnode {
744                    info!(
745                        "Device with pool UUID {pool_uuid}, device UUID {dev_uuid} is no longer available"
746                    );
747                } else {
748                    warn!("Device with pool UUID {pool_uuid}, device UUID {dev_uuid} appears to have been removed but the path did not match the known Stratis device with these identifiers");
749                    self.internal.insert(dev_uuid, LInfo::Luks(linfo));
750                }
751            }
752            Some(LInfo::Stratis(sinfo)) => {
753                if Some(path) == sinfo.luks.as_ref().map(|i| i.dev_info.devnode.as_path()) {
754                    info!("Encrypted backing device with pool UUID {pool_uuid}, device UUID {dev_uuid} is no longer available; removing activated devicemapper device as well");
755                } else if path == sinfo.dev_info.devnode {
756                    if let Some(l) = sinfo.luks {
757                        info!("Encrypted Stratis device with pool UUID {pool_uuid}, device UUID {dev_uuid} is no longer available; marking encrypted backing device as closed");
758                        self.internal.insert(dev_uuid, LInfo::Luks(l));
759                    } else {
760                        info!("Stratis device with pool UUID {pool_uuid}, device UUID {dev_uuid} is no longer available");
761                    }
762                }
763            }
764            _ => (),
765        }
766    }
767
768    pub fn process_info_add(&mut self, info: DeviceInfo) {
772        let stratis_identifiers = info.stratis_identifiers();
773        let device_uuid = stratis_identifiers.device_uuid;
774
775        match self.internal.remove(&device_uuid) {
776            None => {
777                info!(
778                    "Device information {info} discovered and inserted into the set for its pool UUID"
779                );
780                self.internal.insert(device_uuid, info.into());
781            }
782            Some(removed) => match LInfo::update(removed, info) {
783                Either::Right(info) => {
784                    warn!("Found a device information that conflicts with an existing registered device; ignoring");
785                    self.internal.insert(device_uuid, info);
786                }
787                Either::Left(info) => {
788                    info!(
789                        "Device information {info} replaces previous device information for the same device UUID in the set for its pool UUID"
790                    );
791                    self.internal.insert(device_uuid, info);
792                }
793            },
794        }
795    }
796
797    pub fn metadata_version(&self) -> StratisResult<StratSigblockVersion> {
798        let metadata_version = self.iter().fold(HashSet::new(), |mut set, (_, info)| {
799            match info {
800                LInfo::Luks(_) => set.insert(StratSigblockVersion::V1),
801                LInfo::Stratis(info) => set.insert(info.bda.sigblock_version()),
802            };
803            set
804        });
805        if metadata_version.len() > 1 {
806            return Err(StratisError::Msg(
807                "Found two versions of metadata for given device set".to_string(),
808            ));
809        }
810        Ok(*metadata_version
811            .iter()
812            .next()
813            .expect("No empty device sets"))
814    }
815
816    pub fn is_empty(&self) -> bool {
819        self.internal.is_empty()
820    }
821}
822
823impl Into<Value> for &DeviceSet {
824    fn into(self) -> Value {
825        Value::Array(self.internal.values().map(|info| info.into()).collect())
826    }
827}
828
829#[derive(Debug, Eq, PartialEq)]
832pub struct DeviceBag {
833    internal: HashSet<LInfo>,
834}
835
836impl DeviceBag {
837    pub fn remove(&mut self, path: &Path, pool_uuid: PoolUuid, dev_uuid: DevUuid) {
838        self.internal = self
839            .internal
840            .drain()
841            .filter_map(|i| LInfo::update_on_remove(i, path, pool_uuid, dev_uuid))
842            .collect::<HashSet<_>>();
843    }
844
845    pub fn insert(&mut self, info: LInfo) -> bool {
846        self.internal.insert(info)
847    }
848
849    pub fn extend<I: IntoIterator<Item = LInfo>>(&mut self, iter: I) {
850        self.internal.extend(iter)
851    }
852}
853
854impl Into<Value> for &DeviceBag {
855    fn into(self) -> Value {
856        Value::Array(self.internal.iter().map(|info| info.into()).collect())
857    }
858}