stratisd/engine/strat_engine/liminal/
liminal.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5//! Management of devices which are known to stratisd but not in a pool.
6
7use std::{
8    collections::{HashMap, HashSet},
9    os::fd::RawFd,
10    path::PathBuf,
11};
12
13use chrono::{DateTime, Utc};
14use either::Either;
15use libcryptsetup_rs::SafeMemHandle;
16use serde_json::{Map, Value};
17
18use devicemapper::Sectors;
19
20use crate::{
21    engine::{
22        engine::{DumpState, Pool, StateDiff, MAX_STRATIS_PASS_SIZE},
23        shared::read_key_shared,
24        strat_engine::{
25            backstore::{blockdev::InternalBlockDev, find_stratis_devs_by_uuid},
26            crypt::handle::v1::CryptHandle,
27            dm::{
28                has_leftover_devices, has_leftover_devices_legacy, stop_partially_constructed_pool,
29                stop_partially_constructed_pool_legacy,
30            },
31            liminal::{
32                device_info::{
33                    reconstruct_stratis_infos, split_stratis_infos, stratis_infos_ref, DeviceSet,
34                    LInfo, LLuksInfo, LStratisDevInfo, LStratisInfo,
35                },
36                identify::{
37                    bda_wrapper, identify_block_device, DeviceInfo, LuksInfo, StratisDevInfo,
38                    StratisInfo,
39                },
40                setup::{get_blockdevs, get_blockdevs_legacy, get_metadata},
41            },
42            metadata::{StratisIdentifiers, BDA},
43            pool::{v1, v2, AnyPool},
44            serde_structs::{PoolFeatures, PoolSave},
45            shared::tiers_to_bdas,
46            types::BDARecordResult,
47        },
48        structures::Table,
49        types::{
50            DevUuid, LockedPoolsInfo, MaybeInconsistent, Name, PoolEncryptionInfo, PoolIdentifier,
51            PoolUuid, SizedKeyMemory, StoppedPoolsInfo, StratBlockDevDiff, StratSigblockVersion,
52            TokenUnlockMethod, UdevEngineEvent, UuidOrConflict,
53        },
54        BlockDevTier,
55    },
56    stratis::{StratisError, StratisResult},
57};
58
59/// Devices which stratisd has discovered but which have not been assembled
60/// into pools.
61#[derive(Debug, Default, Eq, PartialEq)]
62pub struct LiminalDevices {
63    /// Lookup data structure for pool and device UUIDs corresponding with
64    /// a path where the superblock was either removed or the device was removed.
65    uuid_lookup: HashMap<PathBuf, (PoolUuid, DevUuid)>,
66    /// Devices that have not yet been set up or have been stopped.
67    stopped_pools: HashMap<PoolUuid, DeviceSet>,
68    /// Devices that have been left in a partially constructed state either during start
69    /// or stop.
70    partially_constructed_pools: HashMap<PoolUuid, DeviceSet>,
71    /// Lookup data structure for name to UUID mapping for starting pools by name.
72    name_to_uuid: HashMap<Name, UuidOrConflict>,
73}
74
75impl LiminalDevices {
76    #[allow(dead_code)]
77    fn invariant(&self) {
78        assert!(
79            self.stopped_pools
80                .keys()
81                .cloned()
82                .collect::<HashSet<_>>()
83                .difference(
84                    &self
85                        .uuid_lookup
86                        .iter()
87                        .map(|(_, (u, _))| *u)
88                        .collect::<HashSet<_>>()
89                )
90                .count()
91                == 0
92        );
93    }
94
95    // Unlock the liminal encrypted devices that correspond to the given pool UUID.
96    fn unlock_pool(
97        &mut self,
98        pools: &Table<PoolUuid, AnyPool>,
99        pool_uuid: PoolUuid,
100        token_slot: TokenUnlockMethod,
101        passphrase: Option<&SizedKeyMemory>,
102    ) -> StratisResult<Vec<DevUuid>> {
103        fn handle_luks(
104            luks_info: &LLuksInfo,
105            token_slot: TokenUnlockMethod,
106            passphrase: Option<&SizedKeyMemory>,
107        ) -> StratisResult<()> {
108            if CryptHandle::setup(&luks_info.dev_info.devnode, token_slot, passphrase)?.is_some() {
109                Ok(())
110            } else {
111                Err(StratisError::Msg(format!(
112                    "Block device {} does not appear to be formatted with
113                        the proper Stratis LUKS2 metadata.",
114                    luks_info.dev_info.devnode.display(),
115                )))
116            }
117        }
118
119        let unlocked = match self
120            .stopped_pools
121            .get(&pool_uuid)
122            .or_else(|| self.partially_constructed_pools.get(&pool_uuid))
123        {
124            Some(map) => {
125                let encryption_info = map.encryption_info();
126                if let Ok(None) = encryption_info {
127                    return Err(StratisError::Msg(
128                        format!(
129                            "Attempted to unlock set of devices belonging to an unencrypted pool with UUID {pool_uuid}"
130                        ),
131                    ));
132                } else if let Err(e) = encryption_info {
133                    return Err(StratisError::Chained(
134                        format!(
135                            "Error in the encryption information for pool with UUID {pool_uuid}"
136                        ),
137                        Box::new(e),
138                    ));
139                }
140
141                let mut unlocked = Vec::new();
142                for (dev_uuid, info) in map.iter() {
143                    match info {
144                        LInfo::Stratis(_) => (),
145                        LInfo::Luks(ref luks_info) => {
146                            match handle_luks(luks_info, token_slot, passphrase) {
147                                Ok(()) => unlocked.push(*dev_uuid),
148                                Err(e) => return Err(e),
149                            }
150                        }
151                    }
152                }
153                unlocked
154            }
155            None => match pools.get_by_uuid(pool_uuid) {
156                Some((_, pool)) => {
157                    if pool.is_encrypted() {
158                        vec![]
159                    } else {
160                        return Err(StratisError::Msg(format!(
161                            "Pool with UUID {pool_uuid} is not encrypted and cannot be unlocked."
162                        )));
163                    }
164                }
165                None => {
166                    return Err(StratisError::Msg(format!(
167                        "No devices with UUID {pool_uuid} have been registered with stratisd."
168                    )))
169                }
170            },
171        };
172
173        Ok(unlocked)
174    }
175
176    /// Start a pool, create the devicemapper devices, and return the fully constructed
177    /// legacy pool.
178    ///
179    /// Precondition: Pool was determined to be in stopped or partially constructed pools.
180    pub fn start_pool_legacy(
181        &mut self,
182        pools: &Table<PoolUuid, AnyPool>,
183        pool_uuid: PoolUuid,
184        token_slot: TokenUnlockMethod,
185        passphrase_fd: Option<RawFd>,
186    ) -> StratisResult<(Name, PoolUuid, AnyPool, Vec<DevUuid>)> {
187        fn start_pool_failure(
188            pools: &Table<PoolUuid, AnyPool>,
189            pool_uuid: PoolUuid,
190            luks_info: StratisResult<Option<PoolEncryptionInfo>>,
191            infos: &HashMap<DevUuid, Box<LStratisDevInfo>>,
192            bdas: HashMap<DevUuid, BDA>,
193            meta_res: StratisResult<(DateTime<Utc>, PoolSave)>,
194        ) -> BDARecordResult<(Name, AnyPool)> {
195            let (timestamp, metadata) = match meta_res {
196                Ok(o) => o,
197                Err(e) => return Err((e, bdas)),
198            };
199
200            setup_pool_legacy(
201                pools, pool_uuid, luks_info, infos, bdas, timestamp, metadata,
202            )
203        }
204
205        let pool = self
206            .stopped_pools
207            .get(&pool_uuid)
208            .or_else(|| self.partially_constructed_pools.get(&pool_uuid))
209            .expect("Checked in caller");
210
211        // Here we take a reference to entries in stopped pools because the call to unlock_pool
212        // below requires the pool being unlocked to still have its entry in stopped_pools.
213        // Removing it here would cause an error.
214        let encryption_info = pool.encryption_info();
215        let unlocked_devices = match (encryption_info, token_slot, passphrase_fd) {
216            (Ok(None), TokenUnlockMethod::None, None) => Vec::new(),
217            (Ok(None), _, _) => {
218                return Err(StratisError::Msg(format!(
219                    "Pool with UUID {pool_uuid} is not encrypted but an unlock method or passphrase was provided"
220                )));
221            }
222            (Ok(Some(_)), TokenUnlockMethod::None, _) => {
223                return Err(StratisError::Msg(format!(
224                    "Pool with UUID {pool_uuid} is encrypted but no unlock method was provided"
225                )));
226            }
227            (Ok(Some(_)), method, passphrase_fd) => {
228                let passphrase = if let Some(fd) = passphrase_fd {
229                    let mut memory = SafeMemHandle::alloc(MAX_STRATIS_PASS_SIZE)?;
230                    let len = read_key_shared(fd, memory.as_mut())?;
231                    Some(SizedKeyMemory::new(memory, len))
232                } else {
233                    None
234                };
235
236                self.unlock_pool(pools, pool_uuid, method, passphrase.as_ref())?
237            }
238            (Err(e), _, _) => return Err(e),
239        };
240
241        let uuids = unlocked_devices.into_iter().collect::<Vec<_>>();
242
243        let mut stopped_pool = self
244            .stopped_pools
245            .remove(&pool_uuid)
246            .or_else(|| self.partially_constructed_pools.remove(&pool_uuid))
247            .expect("Checked above");
248        match find_stratis_devs_by_uuid(pool_uuid, &uuids) {
249            Ok(infos) => infos.into_iter().for_each(|(dev_uuid, (path, devno))| {
250                if let Ok(Ok(Some(bda))) = bda_wrapper(&path) {
251                    self.uuid_lookup
252                        .insert(path.to_path_buf(), (pool_uuid, dev_uuid));
253
254                    stopped_pool.process_info_add(DeviceInfo::Stratis(StratisInfo {
255                        dev_info: StratisDevInfo {
256                            device_number: devno,
257                            devnode: path.to_path_buf(),
258                        },
259                        bda,
260                    }));
261                } else {
262                    warn!(
263                        "Failed to read BDA of device with pool UUID {pool_uuid}, dev UUID, {dev_uuid}; ignoring"
264                    );
265                }
266            }),
267            Err(e) => {
268                warn!("Failed to scan for newly unlocked Stratis devices: {e}");
269                return Err(e);
270            }
271        }
272
273        assert!(pools.get_by_uuid(pool_uuid).is_none());
274        assert!(!self.stopped_pools.contains_key(&pool_uuid));
275
276        let luks_info = stopped_pool.encryption_info();
277        let infos = match stopped_pool.into_opened_set() {
278            Either::Left(i) => i,
279            Either::Right(ds) => {
280                let err = StratisError::Msg(format!(
281                    "Some of the devices in pool with UUID {pool_uuid} are unopened"
282                ));
283                info!("Attempt to set up pool failed, but it may be possible to set up the pool later, if the situation changes: {err}");
284                self.handle_stopped_pool(pool_uuid, ds);
285                return Err(err);
286            }
287        };
288
289        let res = load_stratis_metadata(pool_uuid, stratis_infos_ref(&infos));
290        let (infos, bdas) = split_stratis_infos(infos);
291
292        match start_pool_failure(pools, pool_uuid, luks_info, &infos, bdas, res) {
293            Ok((name, pool)) => {
294                self.uuid_lookup = self
295                    .uuid_lookup
296                    .drain()
297                    .filter(|(_, (p, _))| *p != pool_uuid)
298                    .collect();
299                self.name_to_uuid = self
300                    .name_to_uuid
301                    .drain()
302                    .filter_map(|(n, mut maybe_conflict)| {
303                        if maybe_conflict.remove(&pool_uuid) {
304                            None
305                        } else {
306                            Some((n, maybe_conflict))
307                        }
308                    })
309                    .collect();
310                info!("Pool with name \"{name}\" and UUID \"{pool_uuid}\" set up");
311                Ok((name, pool_uuid, pool, uuids))
312            }
313            Err((err, bdas)) => {
314                info!("Attempt to set up pool failed, but it may be possible to set up the pool later, if the situation changes: {err}");
315                let device_set = reconstruct_stratis_infos(infos, bdas);
316                self.handle_stopped_pool(pool_uuid, device_set);
317                Err(err)
318            }
319        }
320    }
321
322    /// Start a pool, create the devicemapper devices, and return the fully constructed
323    /// metadata V2 pool.
324    ///
325    /// Precondition: Pool was determined to be in stopped or partially constructed pools.
326    pub fn start_pool_new(
327        &mut self,
328        pools: &Table<PoolUuid, AnyPool>,
329        pool_uuid: PoolUuid,
330        token_slot: TokenUnlockMethod,
331        passphrase_fd: Option<RawFd>,
332    ) -> StratisResult<(Name, PoolUuid, AnyPool, Vec<DevUuid>)> {
333        fn start_pool_failure(
334            pools: &Table<PoolUuid, AnyPool>,
335            pool_uuid: PoolUuid,
336            infos: &HashMap<DevUuid, Box<LStratisDevInfo>>,
337            bdas: HashMap<DevUuid, BDA>,
338            meta_res: StratisResult<(DateTime<Utc>, PoolSave)>,
339            token_slot: TokenUnlockMethod,
340            passphrase_fd: Option<RawFd>,
341        ) -> BDARecordResult<(Name, AnyPool)> {
342            let (timestamp, metadata) = match meta_res {
343                Ok(o) => o,
344                Err(e) => return Err((e, bdas)),
345            };
346
347            let passphrase = match (
348                metadata.features.contains(&PoolFeatures::Encryption),
349                token_slot,
350                passphrase_fd,
351            ) {
352                (_, _, None) => None,
353                (false, _, _) => {
354                    return Err((
355                        StratisError::Msg(format!(
356                            "Pool with UUID {pool_uuid} is not encrypted but an unlock method or passphrase was provided"
357                        )),
358                        bdas,
359                    ));
360                }
361                (true, TokenUnlockMethod::None, _) => return Err((
362                    StratisError::Msg(
363                        "Metadata reported that encryption enabled but no unlock method was provided"
364                            .to_string()
365                    ),
366                    bdas,
367                )),
368                (true, _, Some(fd)) => {
369                    let mut memory = match SafeMemHandle::alloc(MAX_STRATIS_PASS_SIZE) {
370                        Ok(m) => m,
371                        Err(e) => return Err((StratisError::from(e), bdas)),
372                    };
373                    let len = match read_key_shared(fd, memory.as_mut()) {
374                        Ok(l) => l,
375                        Err(e) => return Err((e, bdas)),
376                    };
377                    Some(SizedKeyMemory::new(memory, len))
378                }
379            };
380
381            setup_pool(
382                pools, pool_uuid, infos, bdas, timestamp, metadata, token_slot, passphrase,
383            )
384        }
385
386        let stopped_pool = self
387            .stopped_pools
388            .remove(&pool_uuid)
389            .or_else(|| self.partially_constructed_pools.remove(&pool_uuid))
390            .expect("Checked above");
391
392        assert!(pools.get_by_uuid(pool_uuid).is_none());
393        assert!(!self.stopped_pools.contains_key(&pool_uuid));
394
395        let infos = stopped_pool
396            .into_opened_set()
397            .expect_left("Cannot fail in V2 of metadata");
398
399        let res = load_stratis_metadata(pool_uuid, stratis_infos_ref(&infos));
400        let (infos, bdas) = split_stratis_infos(infos);
401
402        match start_pool_failure(
403            pools,
404            pool_uuid,
405            &infos,
406            bdas,
407            res,
408            token_slot,
409            passphrase_fd,
410        ) {
411            Ok((name, pool)) => {
412                self.uuid_lookup = self
413                    .uuid_lookup
414                    .drain()
415                    .filter(|(_, (p, _))| *p != pool_uuid)
416                    .collect();
417                self.name_to_uuid = self
418                    .name_to_uuid
419                    .drain()
420                    .filter_map(|(n, mut maybe_conflict)| {
421                        if maybe_conflict.remove(&pool_uuid) {
422                            None
423                        } else {
424                            Some((n, maybe_conflict))
425                        }
426                    })
427                    .collect();
428                info!("Pool with name \"{name}\" and UUID \"{pool_uuid}\" set up");
429                Ok((name, pool_uuid, pool, Vec::new()))
430            }
431            Err((err, bdas)) => {
432                info!("Attempt to set up pool failed, but it may be possible to set up the pool later, if the situation changes: {err}");
433                let device_set = reconstruct_stratis_infos(infos, bdas);
434                self.handle_stopped_pool(pool_uuid, device_set);
435                Err(err)
436            }
437        }
438    }
439
440    /// Start a pool, create the devicemapper devices, and return the fully constructed
441    /// pool.
442    pub fn start_pool(
443        &mut self,
444        pools: &Table<PoolUuid, AnyPool>,
445        id: PoolIdentifier<PoolUuid>,
446        token_slot: TokenUnlockMethod,
447        passphrase_fd: Option<RawFd>,
448    ) -> StratisResult<(Name, PoolUuid, AnyPool, Vec<DevUuid>)> {
449        let pool_uuid = match id {
450            PoolIdentifier::Uuid(u) => u,
451            PoolIdentifier::Name(ref n) => self
452                .name_to_uuid
453                .get(n)
454                .ok_or_else(|| StratisError::Msg(format!("Could not find a pool with name {n}")))
455                .and_then(|uc| uc.to_result())?,
456        };
457        let pool = self
458            .stopped_pools
459            .get(&pool_uuid)
460            .or_else(|| self.partially_constructed_pools.get(&pool_uuid))
461            .ok_or_else(|| {
462                StratisError::Msg(format!(
463                    "Requested pool with UUID {pool_uuid} was not found in stopped or partially constructed pools"
464                ))
465            })?;
466        let metadata_version = pool.metadata_version()?;
467
468        match metadata_version {
469            StratSigblockVersion::V1 => {
470                self.start_pool_legacy(pools, pool_uuid, token_slot, passphrase_fd)
471            }
472            StratSigblockVersion::V2 => {
473                self.start_pool_new(pools, pool_uuid, token_slot, passphrase_fd)
474            }
475        }
476    }
477
478    /// Stop a pool, tear down the devicemapper devices, and store the pool information
479    /// in an internal data structure for later starting.
480    /// Returns true if the pool was torn down entirely, false if the pool is
481    /// partially up. Returns an error if the pool has some untorndown
482    /// filesystems, as in that case the pool needs to be administered.
483    pub fn stop_pool(
484        &mut self,
485        pools: &mut Table<PoolUuid, AnyPool>,
486        pool_name: Name,
487        pool_uuid: PoolUuid,
488        mut pool: AnyPool,
489    ) -> StratisResult<bool> {
490        let res = match pool {
491            AnyPool::V1(ref mut p) => p.stop(&pool_name, pool_uuid),
492            AnyPool::V2(ref mut p) => p.stop(&pool_name, pool_uuid),
493        };
494        let (devices, err) = match res {
495            Ok(devs) => (devs, None),
496            Err((e, true)) => {
497                pools.insert(pool_name, pool_uuid, pool);
498                return Err(e);
499            }
500            Err((e, false)) => {
501                warn!("Failed to stop pool; placing in partially constructed pools");
502                (
503                    match pool {
504                        AnyPool::V1(ref mut p) => DeviceSet::from(p.drain_bds()),
505                        AnyPool::V2(ref mut p) => DeviceSet::from(p.drain_bds()),
506                    },
507                    Some(e),
508                )
509            }
510        };
511        for (_, device) in devices.iter() {
512            match device {
513                LInfo::Luks(l) => {
514                    self.uuid_lookup.insert(
515                        l.dev_info.devnode.clone(),
516                        (l.identifiers.pool_uuid, l.identifiers.device_uuid),
517                    );
518                }
519                LInfo::Stratis(s) => {
520                    self.uuid_lookup.insert(
521                        s.dev_info.devnode.clone(),
522                        (
523                            s.bda.identifiers().pool_uuid,
524                            s.bda.identifiers().device_uuid,
525                        ),
526                    );
527                }
528            }
529        }
530        if err.is_some() {
531            self.partially_constructed_pools.insert(pool_uuid, devices);
532        } else {
533            self.stopped_pools.insert(pool_uuid, devices);
534        }
535        if let Some(maybe_conflict) = self.name_to_uuid.get_mut(&pool_name) {
536            maybe_conflict.add(pool_uuid);
537            if let UuidOrConflict::Conflict(set) = maybe_conflict {
538                warn!("Found conflicting names for stopped pools; UUID will be required to start pools with UUIDs {}", set.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(", "));
539            }
540        } else {
541            self.name_to_uuid
542                .insert(pool_name.clone(), UuidOrConflict::Uuid(pool_uuid));
543        }
544        Ok(err.is_none())
545    }
546
547    /// Tear down a partially constructed pool.
548    pub fn stop_partially_constructed_pool(&mut self, pool_uuid: PoolUuid) -> StratisResult<()> {
549        if let Some(device_set) = self.partially_constructed_pools.remove(&pool_uuid) {
550            let metadata_version = device_set.metadata_version()?;
551            match metadata_version {
552                StratSigblockVersion::V1 => {
553                    match stop_partially_constructed_pool_legacy(
554                        pool_uuid,
555                        &device_set
556                            .iter()
557                            .map(|(dev_uuid, _)| *dev_uuid)
558                            .collect::<Vec<_>>(),
559                    ) {
560                        Ok(_) => {
561                            self.stopped_pools.insert(pool_uuid, device_set);
562                            Ok(())
563                        }
564                        Err(e) => {
565                            warn!("Failed to stop partially constructed pool: {e}");
566                            self.partially_constructed_pools
567                                .insert(pool_uuid, device_set);
568                            Err(e)
569                        }
570                    }
571                }
572                StratSigblockVersion::V2 => match stop_partially_constructed_pool(pool_uuid) {
573                    Ok(_) => {
574                        self.stopped_pools.insert(pool_uuid, device_set);
575                        Ok(())
576                    }
577                    Err(e) => {
578                        warn!("Failed to stop partially constructed pool: {e}");
579                        self.partially_constructed_pools
580                            .insert(pool_uuid, device_set);
581                        Err(e)
582                    }
583                },
584            }
585        } else {
586            Ok(())
587        }
588    }
589
590    /// Get a mapping of pool UUIDs from all of the LUKS2 devices that are currently
591    /// locked to their encryption info in the set of pools that are not yet set up.
592    pub fn locked_pools(&self) -> LockedPoolsInfo {
593        LockedPoolsInfo {
594            name_to_uuid: self
595                .name_to_uuid
596                .iter()
597                .filter_map(|(name, maybe_conflict)| {
598                    maybe_conflict
599                        .to_result()
600                        .ok()
601                        .map(|uuid| (name.clone(), uuid))
602                })
603                .collect::<HashMap<_, _>>(),
604            uuid_to_name: self
605                .name_to_uuid
606                .iter()
607                .filter_map(|(name, maybe_conflict)| {
608                    maybe_conflict
609                        .to_result()
610                        .ok()
611                        .map(|uuid| (uuid, name.clone()))
612                })
613                .collect::<HashMap<_, _>>(),
614            locked: self
615                .stopped_pools
616                .iter()
617                .filter_map(|(pool_uuid, map)| {
618                    map.locked_pool_info().map(|info| (*pool_uuid, info))
619                })
620                .collect(),
621        }
622    }
623
624    /// Get a mapping of pool UUIDs to device sets for all stopped pools.
625    pub fn stopped_pools(&self) -> StoppedPoolsInfo {
626        StoppedPoolsInfo {
627            name_to_uuid: self
628                .name_to_uuid
629                .iter()
630                .filter_map(|(name, maybe_conflict)| {
631                    maybe_conflict
632                        .to_result()
633                        .ok()
634                        .map(|uuid| (name.clone(), uuid))
635                })
636                .collect::<HashMap<_, _>>(),
637            uuid_to_name: self
638                .name_to_uuid
639                .iter()
640                .filter_map(|(name, maybe_conflict)| {
641                    maybe_conflict
642                        .to_result()
643                        .ok()
644                        .map(|uuid| (uuid, name.clone()))
645                })
646                .collect::<HashMap<_, _>>(),
647            stopped: self
648                .stopped_pools
649                .iter()
650                .filter_map(|(pool_uuid, map)| {
651                    map.stopped_pool_info().map(|info| (*pool_uuid, info))
652                })
653                .collect(),
654            partially_constructed: self
655                .partially_constructed_pools
656                .iter()
657                .filter_map(|(pool_uuid, map)| {
658                    map.stopped_pool_info().map(|info| (*pool_uuid, info))
659                })
660                .collect(),
661        }
662    }
663
664    /// Calculate whether block device size has changed.
665    fn handle_size_change<'a, B>(
666        tier: BlockDevTier,
667        dev_uuid: DevUuid,
668        dev: &mut B,
669    ) -> Option<(DevUuid, <<B as DumpState<'a>>::State as StateDiff>::Diff)>
670    where
671        B: DumpState<'a, DumpInput = Sectors> + InternalBlockDev,
672    {
673        if tier == BlockDevTier::Data {
674            let orig = dev.cached();
675            match dev.calc_new_size() {
676                Ok(Some(s)) => Some((dev_uuid, orig.diff(&dev.dump(s)))),
677                Err(e) => {
678                    warn!(
679                        "Failed to determine device size for {}: {}",
680                        dev.physical_path().display(),
681                        e
682                    );
683                    None
684                }
685                _ => None,
686            }
687        } else {
688            None
689        }
690    }
691
692    /// Take maps of pool UUIDs to sets of devices and return a list of
693    /// information about created pools.
694    ///
695    /// Precondition: No pools have yet been set up, i.e., it is unnecessary
696    /// to check for membership in any of the existing categories of device
697    /// sets.
698    pub fn setup_pools(
699        &mut self,
700        all_devices: (
701            HashMap<PoolUuid, Vec<LuksInfo>>,
702            HashMap<PoolUuid, Vec<StratisInfo>>,
703        ),
704    ) -> Vec<(Name, PoolUuid, AnyPool)> {
705        let table = Table::default();
706        let (mut luks_devices, mut stratis_devices) = all_devices;
707
708        let pool_uuids: HashSet<PoolUuid> = luks_devices
709            .keys()
710            .cloned()
711            .collect::<HashSet<PoolUuid>>()
712            .union(&stratis_devices.keys().cloned().collect())
713            .cloned()
714            .collect();
715
716        pool_uuids
717            .iter()
718            .filter_map(|pool_uuid| {
719                let luks_infos = luks_devices.remove(pool_uuid);
720                let stratis_infos = stratis_devices.remove(pool_uuid);
721                let infos: Vec<DeviceInfo> = stratis_infos
722                    .unwrap_or_default()
723                    .drain(..)
724                    .map(DeviceInfo::Stratis)
725                    .chain(
726                        luks_infos
727                            .unwrap_or_default()
728                            .drain(..)
729                            .map(DeviceInfo::Luks),
730                    )
731                    .collect();
732
733                let mut info_map = DeviceSet::new();
734                for info in infos {
735                    match &info {
736                        DeviceInfo::Luks(l) => {
737                            self.uuid_lookup.insert(
738                                l.dev_info.devnode.clone(),
739                                (l.identifiers.pool_uuid, l.identifiers.device_uuid),
740                            );
741                        }
742                        DeviceInfo::Stratis(s) => {
743                            self.uuid_lookup.insert(
744                                s.dev_info.devnode.clone(),
745                                (s.bda.identifiers().pool_uuid, s.bda.identifiers().device_uuid),
746                            );
747                        }
748                    }
749
750                    info_map.process_info_add(info);
751                }
752
753                match info_map.pool_level_metadata_info() {
754                    Ok((MaybeInconsistent::No(Some(name)), _)) => {
755                        if let Some(maybe_conflict) = self.name_to_uuid.get_mut(&name) {
756                            maybe_conflict.add(*pool_uuid);
757                            if let UuidOrConflict::Conflict(set) = maybe_conflict {
758                                warn!("Found conflicting names for stopped pools; UUID will be required to start pools with UUIDs {}", set.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(", "));
759                            }
760                        } else {
761                            self.name_to_uuid
762                                .insert(name, UuidOrConflict::Uuid(*pool_uuid));
763                        }
764                    },
765                    Err(e) => {
766                        info!("Error while attempting to determine pool name for pool with UUID {pool_uuid}: {e}; this may resolve when more devices appear and are processed");
767                    }
768                    _ => (),
769                }
770
771                self.try_setup_started_pool(&table, *pool_uuid, info_map)
772                    .map(|(pool_name, mut pool)| {
773                        match pool {
774                            AnyPool::V1(ref mut p) => {
775                                match p.blockdevs_mut() {
776                                    Ok(blockdevs) => {
777                                        for (dev_uuid, tier, blockdev) in blockdevs {
778                                            if let Some(size) =
779                                                Self::handle_size_change(tier, dev_uuid, blockdev)
780                                                    .and_then(|(_, d)| d.size.changed())
781                                                    .and_then(|c| c)
782                                            {
783                                                blockdev.set_new_size(size);
784                                            }
785                                        }
786                                    }
787                                    Err(e) => {
788                                        warn!("Failed to check size of block devices in newly set up pool: {e}");
789                                    }
790                                }
791                                (pool_name, *pool_uuid, pool)
792                            },
793                            AnyPool::V2(ref mut p) => {
794                                match p.blockdevs_mut() {
795                                    Ok(blockdevs) => {
796                                        for (dev_uuid, tier, blockdev) in blockdevs {
797                                            if let Some(size) =
798                                                Self::handle_size_change(tier, dev_uuid, blockdev)
799                                                    .and_then(|(_, d)| d.size.changed())
800                                                    .and_then(|c| c)
801                                            {
802                                                blockdev.set_new_size(size);
803                                            }
804                                        }
805                                    }
806                                    Err(e) => {
807                                        warn!("Failed to check size of block devices in newly set up pool: {e}");
808                                    }
809                                }
810                                (pool_name, *pool_uuid, pool)
811                            },
812                        }
813                    })
814            })
815            .collect::<Vec<(Name, PoolUuid, AnyPool)>>()
816    }
817
818    /// Variation on try_setup_pool that returns None if the pool is marked
819    /// as stopped in its metadata.
820    ///
821    /// Precondition: pools.get_by_uuid(pool_uuid).is_none() &&
822    ///               self.stopped_pools.get(pool_uuid).is_none()
823    fn try_setup_started_pool(
824        &mut self,
825        pools: &Table<PoolUuid, AnyPool>,
826        pool_uuid: PoolUuid,
827        device_set: DeviceSet,
828    ) -> Option<(Name, AnyPool)> {
829        fn try_setup_started_pool_failure(
830            pools: &Table<PoolUuid, AnyPool>,
831            pool_uuid: PoolUuid,
832            luks_info: StratisResult<Option<PoolEncryptionInfo>>,
833            infos: &HashMap<DevUuid, Box<LStratisDevInfo>>,
834            bdas: HashMap<DevUuid, BDA>,
835            metadata_version: StratisResult<StratSigblockVersion>,
836            meta_res: StratisResult<(DateTime<Utc>, PoolSave)>,
837        ) -> BDARecordResult<Either<(Name, AnyPool), HashMap<DevUuid, BDA>>> {
838            let metadata_version = match metadata_version {
839                Ok(mv) => mv,
840                Err(e) => return Err((e, bdas)),
841            };
842            let (timestamp, metadata) = match meta_res {
843                Ok(o) => o,
844                Err(e) => return Err((e, bdas)),
845            };
846            if let Some(true) | None = metadata.started {
847                match metadata_version {
848                    StratSigblockVersion::V1 => setup_pool_legacy(
849                        pools, pool_uuid, luks_info, infos, bdas, timestamp, metadata,
850                    )
851                    .map(Either::Left),
852                    StratSigblockVersion::V2 => setup_pool(
853                        pools,
854                        pool_uuid,
855                        infos,
856                        bdas,
857                        timestamp,
858                        metadata,
859                        TokenUnlockMethod::None,
860                        None,
861                    )
862                    .map(Either::Left),
863                }
864            } else {
865                Ok(Either::Right(bdas))
866            }
867        }
868
869        assert!(pools.get_by_uuid(pool_uuid).is_none());
870        assert!(!self.stopped_pools.contains_key(&pool_uuid));
871
872        let metadata_version = device_set.metadata_version();
873        let luks_info = device_set.encryption_info();
874        let infos = match device_set.into_opened_set() {
875            Either::Left(i) => i,
876            Either::Right(ds) => {
877                let err = StratisError::Msg(format!(
878                    "Some of the devices in pool with UUID {pool_uuid} are unopened"
879                ));
880                info!("Attempt to set up pool failed, but it may be possible to set up the pool later, if the situation changes: {err}");
881                self.handle_stopped_pool(pool_uuid, ds);
882                return None;
883            }
884        };
885
886        let res = load_stratis_metadata(pool_uuid, stratis_infos_ref(&infos));
887        let (infos, bdas) = split_stratis_infos(infos);
888        match try_setup_started_pool_failure(
889            pools,
890            pool_uuid,
891            luks_info,
892            &infos,
893            bdas,
894            metadata_version,
895            res,
896        ) {
897            Ok(Either::Left((name, pool))) => {
898                self.uuid_lookup = self
899                    .uuid_lookup
900                    .drain()
901                    .filter(|(_, (p, _))| *p != pool_uuid)
902                    .collect();
903                self.name_to_uuid = self
904                    .name_to_uuid
905                    .drain()
906                    .filter_map(|(n, mut maybe_conflict)| {
907                        if maybe_conflict.remove(&pool_uuid) {
908                            None
909                        } else {
910                            Some((n, maybe_conflict))
911                        }
912                    })
913                    .collect();
914                info!("Pool with name \"{name}\" and UUID \"{pool_uuid}\" set up");
915                Some((name, pool))
916            }
917            Ok(Either::Right(bdas)) => {
918                let device_set = reconstruct_stratis_infos(infos, bdas);
919                self.handle_stopped_pool(pool_uuid, device_set);
920                None
921            }
922            Err((err, bdas)) => {
923                info!("Attempt to set up pool failed, but it may be possible to set up the pool later, if the situation changes: {err}");
924                let device_set = reconstruct_stratis_infos(infos, bdas);
925                self.handle_stopped_pool(pool_uuid, device_set);
926                None
927            }
928        }
929    }
930
931    /// On udev events, stratisd checks whether block devices have changed in size.
932    /// This allows us to update the user with the new size if it has changed.
933    /// This method runs a check on device size on each udev change or add event
934    /// to determine whether the size has indeed changed so we can update it in
935    /// our internal data structures.
936    pub fn block_evaluate_size(
937        pools: &mut Table<PoolUuid, AnyPool>,
938        event: &UdevEngineEvent,
939    ) -> StratisResult<Option<(DevUuid, StratBlockDevDiff)>> {
940        let mut ret = None;
941
942        let event_type = event.event_type();
943        let device_path = match event.device().devnode() {
944            Some(d) => d,
945            None => return Ok(None),
946        };
947        let device_info = match event_type {
948            libudev::EventType::Add | libudev::EventType::Change => {
949                if device_path.exists() {
950                    identify_block_device(event)
951                } else {
952                    None
953                }
954            }
955            _ => None,
956        };
957
958        if event_type == libudev::EventType::Add || event_type == libudev::EventType::Change {
959            if let Some(di) = device_info {
960                let pool_uuid = di.stratis_identifiers().pool_uuid;
961                let dev_uuid = di.stratis_identifiers().device_uuid;
962                if let Some((_, pool)) = pools.get_mut_by_uuid(pool_uuid) {
963                    match pool {
964                        AnyPool::V1(p) => {
965                            if let Some((tier, dev)) = p.get_mut_strat_blockdev(dev_uuid)? {
966                                ret = Self::handle_size_change(tier, dev_uuid, dev);
967                            }
968                        }
969                        AnyPool::V2(p) => {
970                            if let Some((tier, dev)) = p.get_mut_strat_blockdev(dev_uuid)? {
971                                ret = Self::handle_size_change(tier, dev_uuid, dev);
972                            }
973                        }
974                    }
975                }
976            }
977        }
978
979        Ok(ret)
980    }
981
982    /// Given some information gathered about a single Stratis device, determine
983    /// whether or not a pool can be constructed, and if it can, construct the
984    /// pool and return the newly constructed pool. If the device appears to
985    /// belong to a pool that has already been set up assume that no further
986    /// processing is required and return None. If there is an error
987    /// constructing the pool, retain the set of devices.
988    pub fn block_evaluate(
989        &mut self,
990        pools: &Table<PoolUuid, AnyPool>,
991        event: &UdevEngineEvent,
992    ) -> Option<(Name, PoolUuid, AnyPool)> {
993        let event_type = event.event_type();
994        let device_path = event.device().devnode()?;
995        let device_info = match event_type {
996            libudev::EventType::Add | libudev::EventType::Change => {
997                if device_path.exists() {
998                    identify_block_device(event)
999                } else {
1000                    None
1001                }
1002            }
1003            _ => None,
1004        };
1005
1006        if event_type == libudev::EventType::Add
1007            || (event_type == libudev::EventType::Change && device_info.is_some())
1008        {
1009            if let Some(info) = device_info {
1010                let stratis_identifiers = info.stratis_identifiers();
1011                let pool_uuid = stratis_identifiers.pool_uuid;
1012                let device_uuid = stratis_identifiers.device_uuid;
1013                if let Some((_, pool)) = pools.get_by_uuid(pool_uuid) {
1014                    match pool {
1015                        AnyPool::V1(p) => {
1016                            if p.get_strat_blockdev(device_uuid).is_none() {
1017                                warn!("Found a device with {info} that identifies itself as belonging to pool with UUID {pool_uuid}, but that pool is already up and running and does not appear to contain the device");
1018                            }
1019                        }
1020                        AnyPool::V2(p) => {
1021                            if p.get_strat_blockdev(device_uuid).is_none() {
1022                                warn!("Found a device with {info} that identifies itself as belonging to pool with UUID {pool_uuid}, but that pool is already up and running and does not appear to contain the device");
1023                            }
1024                        }
1025                    }
1026                    // FIXME: There might be something to check if the device is
1027                    // included in the pool, but that is less clear.
1028                    None
1029                } else {
1030                    let mut devices = self
1031                        .stopped_pools
1032                        .remove(&pool_uuid)
1033                        .or_else(|| self.partially_constructed_pools.remove(&pool_uuid))
1034                        .unwrap_or_default();
1035
1036                    self.uuid_lookup
1037                        .insert(device_path.to_path_buf(), (pool_uuid, device_uuid));
1038
1039                    devices.process_info_add(info);
1040                    match devices.pool_level_metadata_info() {
1041                        Ok((MaybeInconsistent::No(Some(name)), _)) => {
1042                            if let Some(maybe_conflict) = self.name_to_uuid.get_mut(&name) {
1043                                maybe_conflict.add(pool_uuid);
1044                                if let UuidOrConflict::Conflict(set) = maybe_conflict {
1045                                    warn!("Found conflicting names for stopped pools; UUID will be required to start pools with UUIDs {}", set.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(", "));
1046                                }
1047                            } else {
1048                                self.name_to_uuid
1049                                    .insert(name.clone(), UuidOrConflict::Uuid(pool_uuid));
1050                            }
1051                        }
1052                        Err(e) => {
1053                            info!("Error while attempting to determine pool name for pool with UUID {pool_uuid}: {e}; this may resolve when more devices appear and are processed");
1054                        }
1055                        _ => (),
1056                    }
1057                    self.try_setup_started_pool(pools, pool_uuid, devices)
1058                        .map(|(name, pool)| (name, pool_uuid, pool))
1059                }
1060            } else {
1061                None
1062            }
1063        } else if (event_type == libudev::EventType::Change && device_info.is_none())
1064            || event_type == libudev::EventType::Remove
1065        {
1066            let (pool_uuid, dev_uuid) =
1067                if let Some((pool_uuid, dev_uuid)) = self.uuid_lookup.get(device_path) {
1068                    (*pool_uuid, *dev_uuid)
1069                } else {
1070                    return None;
1071                };
1072            if self.stopped_pools.contains_key(&pool_uuid) {
1073                let mut devices = self.stopped_pools.remove(&pool_uuid).unwrap_or_default();
1074
1075                devices.process_info_remove(device_path, pool_uuid, dev_uuid);
1076                self.uuid_lookup.remove(device_path);
1077                match devices.pool_level_metadata_info() {
1078                    Ok((MaybeInconsistent::No(Some(name)), _)) => {
1079                        if let Some(maybe_conflict) = self.name_to_uuid.get_mut(&name) {
1080                            maybe_conflict.add(pool_uuid);
1081                            if let UuidOrConflict::Conflict(set) = maybe_conflict {
1082                                warn!("Found conflicting names for stopped pools; UUID will be required to start pools with UUIDs {}", set.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(", "));
1083                            }
1084                        } else {
1085                            self.name_to_uuid
1086                                .insert(name, UuidOrConflict::Uuid(pool_uuid));
1087                        }
1088                    }
1089                    _ => {
1090                        self.name_to_uuid = self
1091                            .name_to_uuid
1092                            .drain()
1093                            .filter_map(|(n, mut maybe_conflict)| {
1094                                if maybe_conflict.remove(&pool_uuid) {
1095                                    None
1096                                } else {
1097                                    Some((n, maybe_conflict))
1098                                }
1099                            })
1100                            .collect();
1101                    }
1102                }
1103
1104                if !devices.is_empty() {
1105                    self.stopped_pools.insert(pool_uuid, devices);
1106                }
1107            }
1108            None
1109        } else {
1110            None
1111        }
1112    }
1113
1114    pub fn handle_stopped_pool(&mut self, pool_uuid: PoolUuid, device_set: DeviceSet) {
1115        if !device_set.is_empty() {
1116            match device_set.metadata_version() {
1117                Ok(mv) => {
1118                    match mv {
1119                        StratSigblockVersion::V1 => {
1120                            let dev_uuids = device_set
1121                                .iter()
1122                                .map(|(dev_uuid, _)| *dev_uuid)
1123                                .collect::<Vec<_>>();
1124                            if has_leftover_devices_legacy(pool_uuid, &dev_uuids) {
1125                                self.partially_constructed_pools
1126                                    .insert(pool_uuid, device_set);
1127                            } else {
1128                                self.stopped_pools.insert(pool_uuid, device_set);
1129                            }
1130                        }
1131                        StratSigblockVersion::V2 => {
1132                            if has_leftover_devices(pool_uuid) {
1133                                self.partially_constructed_pools
1134                                    .insert(pool_uuid, device_set);
1135                            } else {
1136                                self.stopped_pools.insert(pool_uuid, device_set);
1137                            }
1138                        }
1139                    };
1140                }
1141                Err(e) => {
1142                    warn!("Unable to detect leftover devices: {e}; putting in stopped pools");
1143                    self.stopped_pools.insert(pool_uuid, device_set);
1144                }
1145            }
1146        }
1147    }
1148}
1149
1150impl Into<Value> for &LiminalDevices {
1151    fn into(self) -> Value {
1152        json!({
1153            "stopped_pools": Value::Array(
1154                self.stopped_pools
1155                    .iter()
1156                    .map(|(uuid, set)| {
1157                        json!({
1158                            "pool_uuid": uuid.to_string(),
1159                            "devices": <&DeviceSet as Into<Value>>::into(set),
1160                        })
1161                    })
1162                    .collect()
1163            ),
1164            "partially_constructed_pools": Value::Array(
1165                self.partially_constructed_pools
1166                    .iter()
1167                    .map(|(uuid, set)| {
1168                        json!({
1169                            "pool_uuid": uuid.to_string(),
1170                            "devices": <&DeviceSet as Into<Value>>::into(set),
1171                        })
1172                    })
1173                    .collect()
1174            ),
1175            "path_to_ids_map": Value::Object(
1176                self.uuid_lookup
1177                    .iter()
1178                    .map(|(path, (pool_uuid, dev_uuid))| {
1179                        (
1180                            path.display().to_string(),
1181                            Value::Array(vec![
1182                                Value::from(pool_uuid.to_string()),
1183                                Value::from(dev_uuid.to_string()),
1184                            ]),
1185                        )
1186                    })
1187                    .collect::<Map<_, _>>()
1188            ),
1189            "name_to_pool_uuid_map": Value::Object(
1190                self.name_to_uuid
1191                    .iter()
1192                    .map(|(name, maybe_conflict)| {
1193                        (
1194                            name.to_string(),
1195                            match maybe_conflict {
1196                                UuidOrConflict::Uuid(u) => Value::from(u.to_string()),
1197                                UuidOrConflict::Conflict(set) => Value::from(set.iter().map(|u| Value::from(u.to_string())).collect::<Vec<_>>())
1198                            },
1199                        )
1200                    })
1201                    .collect::<Map<_, _>>()
1202            )
1203        })
1204    }
1205}
1206
1207/// Read the BDA and MDA information for a set of devices that has been
1208/// determined to be a part of the same pool.
1209fn load_stratis_metadata(
1210    pool_uuid: PoolUuid,
1211    infos: HashMap<DevUuid, &LStratisInfo>,
1212) -> StratisResult<(DateTime<Utc>, PoolSave)> {
1213    if let Some((dev_uuid, info)) = infos.iter().find(|(dev_uuid, info)| {
1214        **dev_uuid != info.bda.dev_uuid() || pool_uuid != info.bda.pool_uuid()
1215    }) {
1216        return Err(
1217            StratisError::Msg(format!(
1218                "Mismatch between Stratis identifiers previously read and those found on some BDA: {} != {}",
1219                StratisIdentifiers::new(pool_uuid, *dev_uuid),
1220                StratisIdentifiers::new(info.bda.pool_uuid(), info.bda.dev_uuid())
1221            )));
1222    }
1223
1224    match get_metadata(&infos) {
1225        Ok(opt) => opt
1226            .ok_or_else(|| {
1227                StratisError::Msg(format!(
1228                    "No metadata found on devices associated with pool UUID {pool_uuid}"
1229                ))
1230            }),
1231        Err(err) => Err(StratisError::Chained(
1232            format!(
1233                "There was an error encountered when reading the metadata for the devices found for pool with UUID {pool_uuid}"
1234            ),
1235            Box::new(err),
1236        ))
1237    }
1238}
1239
1240/// Given a set of devices, try to set up a pool.
1241/// Return the pool information if a pool is set up. Otherwise, return
1242/// the pool information to the stopped pools data structure.
1243/// Do not attempt setup if the pool contains any unopened devices.
1244///
1245/// If there is a name conflict between the set of devices in devices
1246/// and some existing pool, return an error.
1247#[allow(clippy::too_many_arguments)]
1248fn setup_pool_legacy(
1249    pools: &Table<PoolUuid, AnyPool>,
1250    pool_uuid: PoolUuid,
1251    luks_info: StratisResult<Option<PoolEncryptionInfo>>,
1252    infos: &HashMap<DevUuid, Box<LStratisDevInfo>>,
1253    bdas: HashMap<DevUuid, BDA>,
1254    timestamp: DateTime<Utc>,
1255    metadata: PoolSave,
1256) -> BDARecordResult<(Name, AnyPool)> {
1257    if let Some((uuid, _)) = pools.get_by_name(&metadata.name) {
1258        return Err((
1259            StratisError::Msg(format!(
1260                "There is a pool name conflict. The devices currently being processed have been identified as belonging to the pool with UUID {} and name {}, but a pool with the same name and UUID {} is already active",
1261                pool_uuid,
1262                &metadata.name,
1263                uuid
1264            )), bdas));
1265    }
1266
1267    let (datadevs, cachedevs) = match get_blockdevs_legacy(&metadata.backstore, infos, bdas) {
1268        Err((err, bdas)) => return Err(
1269            (StratisError::Chained(
1270                format!(
1271                    "There was an error encountered when calculating the block devices for pool with UUID {} and name {}",
1272                    pool_uuid,
1273                    &metadata.name,
1274                ),
1275                Box::new(err)
1276            ), bdas)),
1277        Ok((datadevs, cachedevs)) => (datadevs, cachedevs),
1278    };
1279
1280    let pool_einfo = match luks_info {
1281        Ok(inner) => inner,
1282        Err(_) => {
1283            // NOTE: This is not actually a hopeless situation. It may be
1284            // that a LUKS device owned by Stratis corresponding to a
1285            // Stratis device has just not been discovered yet. If it
1286            // is, the appropriate info will be updated, and setup may
1287            // yet succeed.
1288            return Err((
1289                StratisError::Msg(format!(
1290                        "Some data devices in the set belonging to pool with UUID {} and name {} appear to be encrypted devices managed by Stratis, and some do not",
1291                        pool_uuid,
1292                        &metadata.name
1293                )), tiers_to_bdas(datadevs, cachedevs, None)));
1294        }
1295    };
1296
1297    v1::StratPool::setup(pool_uuid, datadevs, cachedevs, timestamp, &metadata, pool_einfo)
1298        .map(|(name, mut pool)| {
1299            if pool.blockdevs().iter().map(|(_, _, bd)| {
1300                bd.pool_name()
1301            }).any(|name| name != Some(Some(&Name::new(metadata.name.clone()))) || matches!(name, Some(None))) {
1302                if let Err(e) = pool.rename_pool(&name) {
1303                    warn!("Pool will not be able to be started by name; pool name metadata in LUKS2 token is not consistent across all devices: {e}");
1304                }
1305            }
1306            (name, AnyPool::V1(Box::new(pool)))
1307        })
1308        .map_err(|(err, bdas)| {
1309            (StratisError::Chained(
1310                format!(
1311                    "An attempt to set up pool with UUID {pool_uuid} from the assembled devices failed"
1312                ),
1313                Box::new(err),
1314            ), bdas)
1315        })
1316}
1317
1318/// Given a set of devices, try to set up a pool.
1319/// Return the pool information if a pool is set up. Otherwise, return
1320/// the pool information to the stopped pools data structure.
1321/// Do not attempt setup if the pool contains any unopened devices.
1322///
1323/// If there is a name conflict between the set of devices in devices
1324/// and some existing pool, return an error.
1325#[allow(clippy::too_many_arguments)]
1326fn setup_pool(
1327    pools: &Table<PoolUuid, AnyPool>,
1328    pool_uuid: PoolUuid,
1329    infos: &HashMap<DevUuid, Box<LStratisDevInfo>>,
1330    bdas: HashMap<DevUuid, BDA>,
1331    timestamp: DateTime<Utc>,
1332    metadata: PoolSave,
1333    token_slot: TokenUnlockMethod,
1334    passphrase: Option<SizedKeyMemory>,
1335) -> BDARecordResult<(Name, AnyPool)> {
1336    if let Some((uuid, _)) = pools.get_by_name(&metadata.name) {
1337        return Err((
1338                StratisError::Msg(format!(
1339                    "There is a pool name conflict. The devices currently being processed have been identified as belonging to the pool with UUID {} and name {}, but a pool with the same name and UUID {} is already active",
1340                    pool_uuid,
1341                    &metadata.name,
1342                    uuid
1343                )), bdas));
1344    }
1345
1346    let (datadevs, cachedevs) = match get_blockdevs(&metadata.backstore, infos, bdas) {
1347            Err((err, bdas)) => return Err(
1348                (StratisError::Chained(
1349                    format!(
1350                        "There was an error encountered when calculating the block devices for pool with UUID {} and name {}",
1351                        pool_uuid,
1352                        &metadata.name,
1353                    ),
1354                    Box::new(err)
1355                ), bdas)),
1356            Ok((datadevs, cachedevs)) => (datadevs, cachedevs),
1357        };
1358
1359    let dev = datadevs.first();
1360    if dev.is_none() {
1361        return Err((
1362            StratisError::Msg(format!(
1363                "There do not appear to be any data devices in the set with pool UUID {pool_uuid}"
1364            )),
1365            tiers_to_bdas(datadevs, cachedevs, None),
1366        ));
1367    }
1368
1369    v2::StratPool::setup(pool_uuid, datadevs, cachedevs, timestamp, &metadata, token_slot, passphrase)
1370            .map(|(name, pool)| {
1371                (name, AnyPool::V2(Box::new(pool)))
1372            })
1373            .map_err(|(err, bdas)| {
1374                (StratisError::Chained(
1375                    format!(
1376                        "An attempt to set up pool with UUID {pool_uuid} from the assembled devices failed"
1377                    ),
1378                    Box::new(err),
1379                ), bdas)
1380            })
1381}