1use 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#[derive(Debug, Default, Eq, PartialEq)]
62pub struct LiminalDevices {
63 uuid_lookup: HashMap<PathBuf, (PoolUuid, DevUuid)>,
66 stopped_pools: HashMap<PoolUuid, DeviceSet>,
68 partially_constructed_pools: HashMap<PoolUuid, DeviceSet>,
71 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
1207fn 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#[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 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#[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}