<NME> blockdevmgr.rs
<BEF> // This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Code to handle a collection of block devices.

#[cfg(test)]
use std::collections::HashSet;

use std::{collections::HashMap, fs, path::PathBuf};

use chrono::{DateTime, Duration, Utc};
use rand::{seq::IteratorRandom, thread_rng};
use serde_json::Value;
use tempfile::TempDir;

use devicemapper::{Bytes, Device, LinearDevTargetParams, LinearTargetParams, Sectors, TargetLine};

use crate::{
    engine::{
        shared::gather_encryption_info,
        strat_engine::{
            backstore::{
                blockdev::StratBlockDev,
                crypt::{
                    back_up_luks_header, interpret_clevis_config, restore_luks_header,
                    CryptActivationHandle,
                },
                devices::{initialize_devices, wipe_blockdevs, UnownedDevices},
                range_alloc::PerDevSegments,
                transaction::RequestTransaction,
            },
            metadata::MDADataSize,
            serde_structs::{BaseBlockDevSave, BaseDevSave, Recordable},
        },
        types::{
            ActionAvailability, DevUuid, EncryptionInfo, KeyDescription, PoolEncryptionInfo,
            PoolUuid,
        },
    },
    stratis::{StratisError, StratisResult},
};

const MAX_NUM_TO_WRITE: usize = 10;

/// struct to represent a continuous set of sectors on a disk
#[derive(Debug, Clone)]
pub struct Segment {
    /// The offset into the device where this segment starts.
    pub(super) start: Sectors,
    /// The length of the segment.
    pub(super) length: Sectors,
    /// The device the segment is within.
    pub(super) device: Device,
}

impl Segment {
    /// Create a new Segment with given attributes
    pub fn new(device: Device, start: Sectors, length: Sectors) -> Segment {
        Segment {
            start,
            length,
            device,
        }
    }
}

#[derive(Clone, Debug)]
pub struct BlkDevSegment {
    pub(super) uuid: DevUuid,
    pub(super) segment: Segment,
}

impl BlkDevSegment {
    pub fn new(uuid: DevUuid, segment: Segment) -> BlkDevSegment {
        BlkDevSegment { uuid, segment }
    }

    pub fn to_segment(&self) -> Segment {
        self.segment.clone()
    }
}

impl Recordable<Vec<BaseDevSave>> for Vec<BlkDevSegment> {
    fn record(&self) -> Vec<BaseDevSave> {
        self.iter()
            .map(|bseg| BaseDevSave {
                parent: bseg.uuid,
                start: bseg.segment.start,
                length: bseg.segment.length,
            })
            .collect::<Vec<_>>()
    }
}

/// Build a linear dev target table from BlkDevSegments. This is useful for
/// calls to the devicemapper library.
pub fn map_to_dm(bsegs: &[BlkDevSegment]) -> Vec<TargetLine<LinearDevTargetParams>> {
    let mut table = Vec::new();
    let mut logical_start_offset = Sectors(0);

    let segments = bsegs
        .iter()
        .map(|bseg| bseg.to_segment())
        .collect::<Vec<_>>();
    for segment in segments {
        let (physical_start_offset, length) = (segment.start, segment.length);
        let params = LinearTargetParams::new(segment.device, physical_start_offset);
        let line = TargetLine::new(
            logical_start_offset,
            length,
            LinearDevTargetParams::Linear(params),
        );
        table.push(line);
        logical_start_offset += length;
    }

    table
}

#[derive(Debug)]
pub struct BlockDevMgr {
    /// All the block devices that belong to this block dev manager.
    block_devs: Vec<StratBlockDev>,
    /// The most recent time that variable length metadata was saved to the
    /// devices managed by this block dev manager.
    last_update_time: Option<DateTime<Utc>>,
}

impl BlockDevMgr {
    /// Make a struct that represents an existing BlockDevMgr.
    pub fn new(
        block_devs: Vec<StratBlockDev>,
        last_update_time: Option<DateTime<Utc>>,
    ) -> BlockDevMgr {
        BlockDevMgr {
            block_devs,
            last_update_time,
        }
    }

    /// Initialize a new StratBlockDevMgr with specified pool and devices.
    pub fn initialize(
        pool_uuid: PoolUuid,
        devices: UnownedDevices,
        mda_data_size: MDADataSize,
        encryption_info: Option<&EncryptionInfo>,
    ) -> StratisResult<BlockDevMgr> {
        Ok(BlockDevMgr::new(
            initialize_devices(devices, pool_uuid, mda_data_size, encryption_info)?,
            None,
        ))
    }

    /// Get a hashmap that maps UUIDs to Devices.
    pub fn uuid_to_devno(&self) -> HashMap<DevUuid, Device> {
        self.block_devs
            .iter()
            .map(|bd| (bd.uuid(), *bd.device()))
            .collect()
    }

    /// Check that the registered key description and Clevis information for these
    /// block devices can unlock at least one of the existing block devices registered.
    /// Precondition: self.block_devs must have at least one device.
    pub fn can_unlock(&self, try_unlock_keyring: bool, try_unlock_clevis: bool) -> bool {
        CryptActivationHandle::can_unlock(
            self.block_devs
                .get(0)
                .expect("Must have at least one blockdev")
                .physical_path(),
            try_unlock_keyring,
            try_unlock_clevis,
        )
    }

    /// Add paths to self.
    /// Return the uuids of all blockdevs corresponding to paths that were
    /// added.
    pub fn add(
        &mut self,
        pool_uuid: PoolUuid,
        devices: UnownedDevices,
    ) -> StratisResult<Vec<DevUuid>> {
        let this_pool_uuid = self.block_devs.get(0).map(|bd| bd.pool_uuid());
        if this_pool_uuid.is_some() && this_pool_uuid != Some(pool_uuid) {
            return Err(StratisError::Msg(
                format!("block devices being managed have pool UUID {} but new devices are to be added with pool UUID {}",
                        this_pool_uuid.expect("guarded by if-expression"),
                        pool_uuid)
            ));
        }

        let encryption_info = pool_enc_to_enc!(self.encryption_info());
        if let Some(ref ei) = encryption_info {
            if !self.can_unlock(ei.key_description().is_some(), ei.clevis_info().is_some()) {
                return Err(StratisError::Msg(
                    "Neither the key in the kernel keyring nor Clevis could be used to perform encryption operations on the devices in the pool; check that either the appropriate key in the keyring is set or that the Clevis key storage method is available".to_string(),
                ));
            }
        }

        // FIXME: This is a bug. If new devices are added to a pool, and the
        // variable length metadata requires more than the minimum allocated,
        // then the necessary amount must be provided or the data can not be
        // saved.
        let bds = initialize_devices(
            devices,
            pool_uuid,
            MDADataSize::default(),
            encryption_info.as_ref(),
        )?;
        let bdev_uuids = bds.iter().map(|bd| bd.uuid()).collect();
        self.block_devs.extend(bds);
        Ok(bdev_uuids)
    }

    pub fn destroy_all(&mut self) -> StratisResult<()> {
        wipe_blockdevs(&mut self.block_devs)
    }

    /// Remove the specified block devs and erase their metadata.
    ///
    /// Precondition: It is the responsibility of the caller to ensure that
    /// none of the blockdevs are in use, that is, have had any space allocated
    /// from them for upper layers.
    ///
    /// If a specified blockdev is not found, returns an error and does nothing.
    ///
    /// NOTE: This method traverses the block_devs Vec from the rear to the
    /// front, looking for blockdevs to remove. This is algorithmically
    /// inefficient, unless it is assumed that the blockdevs specified are very
    /// near the end of the Vec, which is expected to be the case. In that case,
    /// the algorithm is O(n).
    pub(super) fn remove_blockdevs(&mut self, uuids: &[DevUuid]) -> StratisResult<()> {
        let mut removed = Vec::new();
        for uuid in uuids {
            let mut found = false;
            let blockdevs_last_index = self.block_devs.len() - 1;
            for i in 0..blockdevs_last_index {
                let index = blockdevs_last_index - i;
                if self.block_devs[index].uuid() == *uuid {
                    removed.push(self.block_devs.swap_remove(index));
                    found = true;
                    break;
                }
            }
            if !found {
                return Err(StratisError::Msg(format!(
                    "Blockdev corresponding to UUID: {} not found.",
                    uuid
                )));
            }
        }
        wipe_blockdevs(&mut removed)?;
        Ok(())
    }

    /// Allocate space according to sizes vector request.
    /// Return the segments allocated for each request, or None if it was
    /// not possible to satisfy the request.
    /// This method is atomic, it either allocates all requested or allocates
    /// nothing.
    pub fn request_space(&self, sizes: &[Sectors]) -> StratisResult<Option<RequestTransaction>> {
        let mut transaction = RequestTransaction::default();

        let total_needed: Sectors = sizes.iter().cloned().sum();
        if self.avail_space() < total_needed {
            return Ok(None);
        }

        for (idx, &needed) in sizes.iter().enumerate() {
            let mut alloc = Sectors(0);
            // TODO: Consider greater efficiency for allocation generally.
            // Over time, the blockdevs at the start will be exhausted. It
            // might be a good idea to keep an auxiliary structure, so that
            // only blockdevs with some space left to allocate are accessed.
            // In the context of this major inefficiency that ensues over time
            // the obvious but more minor inefficiency of this inner loop is
            // not worth worrying about.
            for bd in &self.block_devs {
                if alloc == needed {
                    break;
                }

                let r_segs = bd.request_space(needed - alloc, &transaction)?;
                for (&start, &length) in r_segs.iter() {
                    transaction.add_bd_seg_req(
                        idx,
                        BlkDevSegment::new(bd.uuid(), Segment::new(*bd.device(), start, length)),
                    );
                }
                alloc += r_segs.sum();
            }
            assert_eq!(alloc, needed);
        }

        Ok(Some(transaction))
    }

    /// Commit the allocations calculated by the request_space() method.
    ///
    /// This method converts the block device segments into the necessary data
    /// structure and dispatches them to the corresponding block devices to
    /// update the internal records of allocated space.
    pub fn commit_space(&mut self, mut transaction: RequestTransaction) -> StratisResult<()> {
        let mut segs = transaction.drain_blockdevmgr().try_fold(
            HashMap::<DevUuid, PerDevSegments>::new(),
            |mut map, seg| -> StratisResult<_> {
                if let Some(segs) = map.get_mut(&seg.uuid) {
                    segs.insert(&(seg.segment.start, seg.segment.length))?;
                } else {
                    let mut segs = PerDevSegments::new(
                        self.block_devs
                            .iter()
                            .find(|bd| bd.uuid() == seg.uuid)
                            .expect(
                                "Block dev was determined to be present during allocation request",
                            )
                            .total_size()
                            .sectors(),
                    );
                    segs.insert(&(seg.segment.start, seg.segment.length))?;
                    map.insert(seg.uuid, segs);
                }

                Ok(map)
            },
        )?;

        for (uuid, bd) in self.blockdevs_mut() {
            if let Some(segs) = segs.remove(&uuid) {
                bd.commit_space(segs);
            }
        }

        Ok(())
    }

    /// Write the given data to all blockdevs marking with current time.
    /// Return an error if data was not written to any blockdev.
    /// Omit blockdevs which do not have sufficient space in BDA to accommodate
    /// metadata. If current time is not more recent than previously written
    /// time, use a time that is one nanosecond greater than that previously
    /// written. Randomly select no more than MAX_NUM_TO_WRITE blockdevs to
    /// write to.
    pub fn save_state(&mut self, metadata: &[u8]) -> StratisResult<()> {
        let current_time = Utc::now();
        let stamp_time = if Some(current_time) <= self.last_update_time {
            self.last_update_time
                .expect("self.last_update_time >= Some(current_time")
                .checked_add_signed(Duration::nanoseconds(1))
                .expect("self.last_update_time << maximum representable DateTime")
        } else {
            current_time
        };

        let data_size = Bytes::from(metadata.len());
        let candidates = self
            .block_devs
            .iter_mut()
            .filter(|b| b.max_metadata_size().bytes() >= data_size);

        // TODO: consider making selection not entirely random, i.e, ensuring
        // distribution of metadata over different paths.
        let saved = candidates
            .choose_multiple(&mut thread_rng(), MAX_NUM_TO_WRITE)
            .iter_mut()
            .fold(false, |acc, b| {
                acc | b.save_state(&stamp_time, metadata).is_ok()
            });

        if saved {
            self.last_update_time = Some(stamp_time);
            Ok(())
        } else {
            let err_msg = "Failed to save metadata to even one device in pool";
            Err(StratisError::Msg(err_msg.into()))
        }
    }

    /// Get references to managed blockdevs.
    pub fn blockdevs(&self) -> Vec<(DevUuid, &StratBlockDev)> {
        self.block_devs.iter().map(|bd| (bd.uuid(), bd)).collect()
    }

    pub fn blockdevs_mut(&mut self) -> Vec<(DevUuid, &mut StratBlockDev)> {
        self.block_devs
            .iter_mut()
            .map(|bd| (bd.uuid(), bd as &mut StratBlockDev))
            .collect()
    }

    pub fn get_blockdev_by_uuid(&self, uuid: DevUuid) -> Option<&StratBlockDev> {
        self.block_devs.iter().find(|bd| bd.uuid() == uuid)
    }

    pub fn get_mut_blockdev_by_uuid(&mut self, uuid: DevUuid) -> Option<&mut StratBlockDev> {
        self.block_devs.iter_mut().find(|bd| bd.uuid() == uuid)
    }

    // SIZE methods

    /// The number of sectors not allocated for any purpose.
    pub fn avail_space(&self) -> Sectors {
        self.block_devs.iter().map(|bd| bd.available()).sum()
    }

    /// The current size of all the blockdevs.
    /// self.size() > self.avail_space() because some sectors are certainly
    /// allocated for Stratis metadata
    pub fn size(&self) -> Sectors {
        self.block_devs
            .iter()
            .map(|b| b.total_size().sectors())
            .sum()
    }

    /// The number of sectors given over to Stratis metadata
    /// self.allocated_size() - self.metadata_size() >= self.avail_space()
    pub fn metadata_size(&self) -> Sectors {
        self.block_devs
            .iter()
            .map(|bd| bd.metadata_size().sectors())
            .sum()
    }

    /// Get the encryption information for a whole pool.
    pub fn encryption_info(&self) -> Option<PoolEncryptionInfo> {
        gather_encryption_info(
            self.block_devs.len(),
            self.block_devs.iter().map(|bd| bd.encryption_info()),

    let mut bds: Vec<StratBlockDev> = Vec::new();
    for (dev, (devnode, dev_size, mut f)) in add_devs {
        let dev_size = dev_size.sectors();

        let bda = BDA::initialize(&mut f,
                                  pool_uuid,
                                  Uuid::new_v4(),
                                  mda_size,
                                  dev_size,
                                  Utc::now().timestamp() as u64);
        if let Ok(bda) = bda {
            let hw_id = match hw_lookup(devnode) {
            .block_devs
            .iter()
            .map(|bd| bd.pool_uuid())
            .collect::<HashSet<_>>();
        assert!(pool_uuids.len() == 1);
            // The dev_size is at least MIN_DEV_SIZE, but the size of the
            // metadata is not really bounded from above.
            let blockdev =
                StratBlockDev::new(dev, devnode.to_owned(), dev_size, bda, &[], None, hw_id)
                    .expect("bda.size() < dev_size and no user facing segments allocated");
            bds.push(blockdev);
        } else {
            // TODO: check the return values and update state machine on failure
        } else {
            assert_eq!(encryption_infos.len(), self.block_devs.len());

            let info_set = encryption_infos.iter().collect::<HashSet<_>>();
            assert!(info_set.len() == 1);
        }

        for bd in self.block_devs.iter() {
            bd.invariant();
        }
    }

    /// Bind all devices in the given blockdev manager using the given clevis
    /// configuration.
    ///
    /// * Returns Ok(true) if the binding was performed.
    /// * Returns Ok(false) if the binding had already been previously performed and
    /// nothing was changed.
    /// * Returns Err(_) if an inconsistency was found in the metadata across pools
    /// or binding failed.
    pub fn bind_clevis(&mut self, pin: &str, clevis_info: &Value) -> StratisResult<bool> {
        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {
            Some(ei) => ei,
            None => {
                return Err(StratisError::Msg(
                    "Requested pool does not appear to be encrypted".to_string(),
                ));
            }
        };

        let mut parsed_config = clevis_info.clone();
        let yes = interpret_clevis_config(pin, &mut parsed_config)?;

        if let Some((ref existing_pin, ref existing_info)) = encryption_info.clevis_info() {
            // Ignore thumbprint if stratis:tang:trust_url is set in the clevis_info
            // config.
            let mut config_to_check = existing_info.clone();
            if yes {
                if let Value::Object(ref mut ei) = config_to_check {
                    ei.remove("thp");
                }
            }

            if (existing_pin.as_str(), &config_to_check) == (pin, &parsed_config)
                && self.can_unlock(false, true)
            {
                Ok(false)
            } else {
                Err(StratisError::Msg(format!(
                    "Block devices have already been bound with pin {} and config {}; \
                        requested pin {} and config {} can't be applied",
                    existing_pin, existing_info, pin, parsed_config,
                )))
            }
        } else {
            operation_loop(
                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),
                |blockdev| blockdev.bind_clevis(pin, clevis_info),
            )?;
            Ok(true)
        }
    }

    /// Unbind all devices in the given blockdev manager from clevis.
    ///
    /// * Returns Ok(true) if the unbinding was performed.
    /// * Returns Ok(false) if the unbinding had already been previously performed and
    /// nothing was changed.
    /// * Returns Err(_) if an inconsistency was found in the metadata across pools
    /// or unbinding failed.
    pub fn unbind_clevis(&mut self) -> StratisResult<bool> {
        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {
            Some(ei) => ei,
            None => {
                return Err(StratisError::Msg(
                    "Requested pool does not appear to be encrypted".to_string(),
                ));
            }
        };

        if encryption_info.clevis_info().is_some() {
            operation_loop(
                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),
                |blockdev| blockdev.unbind_clevis(),
            )?;
            Ok(true)
        } else {
            Ok(false)
        }
    }

    /// Bind all devices in the given blockdev manager to a passphrase using the
    /// given key description.
    ///
    /// * Returns Ok(true) if the binding was performed.
    /// * Returns Ok(false) if the binding had already been previously performed and
    /// nothing was changed.
    /// * Returns Err(_) if an inconsistency was found in the metadata across pools
    /// or binding failed.
    pub fn bind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult<bool> {
        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {
            Some(ei) => ei,
            None => {
                return Err(StratisError::Msg(
                    "Requested pool does not appear to be encrypted".to_string(),
                ));
            }
        };

        if let Some(kd) = encryption_info.key_description() {
            if kd == key_desc {
                if self.can_unlock(true, false) {
                    Ok(false)
                } else {
                    Err(StratisError::Msg(format!(
                        "Key description {} is registered in the metadata but the \
                            associated passphrase can't unlock the device; the \
                            associated passphrase may have changed since binding",
                        key_desc.as_application_str(),
                    )))
                }
            } else {
                Err(StratisError::Msg(format!(
                    "Block devices have already been bound with key description {}; \
                        requested key description {} can't be applied",
                    key_desc.as_application_str(),
                    kd.as_application_str(),
                )))
            }
        } else {
            operation_loop(
                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),
                |blockdev| blockdev.bind_keyring(key_desc),
            )?;
            Ok(true)
        }
    }

    /// Unbind all devices in the given blockdev manager from the passphrase
    /// associated with the key description.
    ///
    /// * Returns Ok(true) if the unbinding was performed.
    /// * Returns Ok(false) if the unbinding had already been previously performed and
    /// nothing was changed.
    /// * Returns Err(_) if an inconsistency was found in the metadata across pools
    /// or unbinding failed.
    pub fn unbind_keyring(&mut self) -> StratisResult<bool> {
        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {
            Some(ei) => ei,
            None => {
                return Err(StratisError::Msg(
                    "Requested pool does not appear to be encrypted".to_string(),
                ));
            }
        };

        if encryption_info.key_description().is_some() {
            operation_loop(
                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),
                |blockdev| blockdev.unbind_keyring(),
            )?;
            Ok(true)
        } else {
            // is encrypted and key description is None
            Ok(false)
        }
    }

    /// Change the keyring passphrase associated with the block devices in
    /// this pool.
    ///
    /// Returns:
    /// * Ok(None) if the pool is not currently bound to a keyring passphrase.
    /// * Ok(Some(true)) if the pool was successfully bound to the new key description.
    /// * Ok(Some(false)) if the pool is already bound to this key description.
    /// * Err(_) if an operation fails while changing the passphrase.
    pub fn rebind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult<Option<bool>> {
        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {
            Some(ei) => ei,
            None => {
                return Err(StratisError::Msg(
                    "Requested pool does not appear to be encrypted".to_string(),
                ));
            }
        };

        if encryption_info.key_description() == Some(key_desc) {
            Ok(Some(false))
        } else if encryption_info.key_description().is_some() {
            // Keys are not the same but key description is present
            operation_loop(
                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),
                |blockdev| blockdev.rebind_keyring(key_desc),
            )?;
            Ok(Some(true))
        } else {
            Ok(None)
        }
    }

    /// Regenerate the Clevis bindings with the block devices in this pool using
    /// the same configuration.
    ///
    /// The method for this rollback caches the initial Clevis metadata and
    /// reverts all of the devices if there is a failure.
    ///
    /// This method returns StratisResult<()> because the Clevis regen command
    /// will always change the metadata when successful. The command is not idempotent
    /// so this method will either fail to regenerate the bindings or it will
    /// result in a metadata change.
    pub fn rebind_clevis(&mut self) -> StratisResult<()> {
        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {
            Some(ei) => ei,
            None => {
                return Err(StratisError::Msg(
                    "Requested pool does not appear to be encrypted".to_string(),
                ));
            }
        };

        if encryption_info.clevis_info().is_none() {
            Err(StratisError::Msg(
                "Requested pool is not already bound to Clevis".to_string(),
            ))
        } else {
            operation_loop(
                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),
                |blockdev| blockdev.rebind_clevis(),
            )?;

            Ok(())
        }
    }

    pub fn grow(&mut self, dev: DevUuid) -> StratisResult<bool> {
        let bd = self
            .block_devs
            .iter_mut()
            .find(|bd| bd.uuid() == dev)
            .ok_or_else(|| {
                StratisError::Msg(format!("Block device with UUID {} not found", dev))
            })?;
        bd.grow()
    }

    /// Tear down devicemapper devices for the block devices in this BlockDevMgr.
    pub fn teardown(&mut self) -> StratisResult<()> {
        let mut errors = Vec::new();
        for bd in self.block_devs.iter_mut() {
            if let Err(e) = bd.teardown() {
                errors.push(e);
            }
        }

        if errors.is_empty() {
            Ok(())
        } else {
            Err(StratisError::BestEffortError("Failed to remove devicemapper devices for some or all physical devices in the pool".to_string(), errors))
        }
    }
}

fn operation_loop<'a, I, A>(blockdevs: I, action: A) -> StratisResult<()>
where
    I: IntoIterator<Item = &'a mut StratBlockDev>,
    A: Fn(&mut StratBlockDev) -> StratisResult<()>,
{
    fn rollback_loop(
        rollback_record: Vec<&mut StratBlockDev>,
        headers: Vec<PathBuf>,
        causal_error: StratisError,
    ) -> StratisError {
        // NOTE: Zip can be used here because the header will always be backed up before
        // the operation is performed. As a result, the header iterator will always be
        // equal to or longer than the blockdev record iterator which means all blockdevs
        // that have had operations performed on them will always be restored.
        for (blockdev, header) in rollback_record.into_iter().zip(headers) {
            if let Err(e) = restore_luks_header(blockdev.devnode(), header.as_path()) {
                warn!(
                    "Failed to roll back device operation for device {}: {}",
                    blockdev.physical_path().display(),
                    e,
                );
                return StratisError::RollbackError {
                    causal_error: Box::new(causal_error),
                    rollback_error: Box::new(e),
                    level: ActionAvailability::NoRequests,
                };
            }
        }

        causal_error
    }

    fn perform_operation<'a, I, A>(tmp_dir: &TempDir, blockdevs: I, action: A) -> StratisResult<()>
    where
        I: IntoIterator<Item = &'a mut StratBlockDev>,
        A: Fn(&mut StratBlockDev) -> StratisResult<()>,
    {
        let mut original_headers = Vec::new();
        let mut rollback_record = Vec::new();
        for blockdev in blockdevs {
            match back_up_luks_header(blockdev.physical_path(), tmp_dir) {
                Ok(h) => original_headers.push(h),
                Err(e) => return Err(rollback_loop(rollback_record, original_headers, e)),
            };
            let res = action(blockdev);
            rollback_record.push(blockdev);
            if let Err(error) = res {
                return Err(rollback_loop(rollback_record, original_headers, error));
            }
        }

        Ok(())
    }

    let tmp_dir = TempDir::new()?;
    let res = perform_operation(&tmp_dir, blockdevs, action);
    if let Err(e) = fs::remove_dir_all(tmp_dir.path()) {
        warn!(
            "Leaked temporary files at path {}: {}",
            tmp_dir.path().display(),
            e
        );
    }
    res
}

impl Recordable<Vec<BaseBlockDevSave>> for BlockDevMgr {
    fn record(&self) -> Vec<BaseBlockDevSave> {
        self.block_devs.iter().map(|bd| bd.record()).collect()
    }
}

#[cfg(test)]
mod tests {
    use std::{env, error::Error, path::Path};

    use crate::engine::strat_engine::{
        backstore::devices::{ProcessedPathInfos, UnownedDevices},
        cmd,
        names::KeyDescription,
        ns::{unshare_namespace, MemoryFilesystem},
        tests::{crypt, loopbacked, real},
    };

    use super::*;

    fn get_devices(paths: &[&Path]) -> StratisResult<UnownedDevices> {
        ProcessedPathInfos::try_from(paths)
            .map(|ps| ps.unpack())
            .and_then(|(sds, uds)| sds.error_on_not_empty().map(|_| uds))
    }

    /// Verify that initially,
    /// size() - metadata_size() = avail_space().
    /// After 2 Sectors have been allocated, that amount must also be included
    /// in balance.
    fn test_blockdevmgr_used(paths: &[&Path]) {
        let pool_uuid = PoolUuid::new_v4();
        let devices = get_devices(paths).unwrap();
        let mut mgr =
            BlockDevMgr::initialize(pool_uuid, devices, MDADataSize::default(), None).unwrap();
        assert_eq!(mgr.avail_space() + mgr.metadata_size(), mgr.size());

        let allocated = Sectors(2);
        let transaction = mgr.request_space(&[allocated]).unwrap().unwrap();
        mgr.commit_space(transaction).unwrap();
        assert_eq!(
            mgr.avail_space() + allocated + mgr.metadata_size(),
            mgr.size()
        );
    }

    #[test]
    fn loop_test_blockdevmgr_used() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Range(1, 3, None),
            test_blockdevmgr_used,
        );
    }

    #[test]
    fn real_test_blockdevmgr_used() {
        real::test_with_spec(
            &real::DeviceLimits::AtLeast(1, None, None),
            test_blockdevmgr_used,
        );
    }

    /// Test that the `BlockDevMgr` will add devices if the same key
    /// is used to encrypted the existing devices and the added devices.
    fn test_blockdevmgr_same_key(paths: &[&Path]) {
        fn test_with_key(paths: &[&Path], key_desc: &KeyDescription) -> Result<(), Box<dyn Error>> {
            let pool_uuid = PoolUuid::new_v4();

            let devices1 = get_devices(&paths[..2])?;
            let devices2 = get_devices(&paths[2..3])?;

            let mut bdm = BlockDevMgr::initialize(
                pool_uuid,
                devices1,
                MDADataSize::default(),
                Some(&EncryptionInfo::KeyDesc(key_desc.clone())),
            )?;

            if bdm.add(pool_uuid, devices2).is_err() {
                Err(Box::new(StratisError::Msg(
                    "Adding a blockdev with the same key to an encrypted pool should succeed"
                        .to_string(),
                )))
            } else {
                Ok(())
            }
        }

        crypt::insert_and_cleanup_key(paths, test_with_key);
    }

    #[test]
    fn loop_test_blockdevmgr_same_key() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(3, None),
            test_blockdevmgr_same_key,
        );
    }

    #[test]
    fn real_test_blockdevmgr_same_key() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(3, None, None),
            test_blockdevmgr_same_key,
        );
    }

    /// Test that the `BlockDevMgr` will not add devices if a different key
    /// is present in the keyring than was used to encrypted the existing
    /// devices.
    fn test_blockdevmgr_changed_key(paths: &[&Path]) {
        fn test_with_key(paths: &[&Path], key_desc: &KeyDescription) -> Result<(), Box<dyn Error>> {
            let pool_uuid = PoolUuid::new_v4();

            let devices1 = get_devices(&paths[..2])?;
            let devices2 = get_devices(&paths[2..3])?;

            let mut bdm = BlockDevMgr::initialize(
                pool_uuid,
                devices1,
                MDADataSize::default(),
                Some(&EncryptionInfo::KeyDesc(key_desc.clone())),
            )?;

            crypt::change_key(key_desc)?;

            if bdm.add(pool_uuid, devices2).is_ok() {
                Err(Box::new(StratisError::Msg(
                    "Adding a blockdev with a new key to an encrypted pool should fail".to_string(),
                )))
            } else {
                Ok(())
            }
        }

        crypt::insert_and_cleanup_key(paths, test_with_key);
    }

    #[test]
    fn loop_test_blockdevmgr_changed_key() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(3, None),
            test_blockdevmgr_changed_key,
        );
    }

    #[test]
    fn real_test_blockdevmgr_changed_key() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(3, None, None),
            test_blockdevmgr_changed_key,
        );
    }

    /// Verify that it is impossible to steal blockdevs from another Stratis
    /// pool.
    /// 1. Initialize devices with pool uuid.
    /// 2. Initializing again with different uuid must fail.
    /// 3. Adding the devices must succeed, because they already belong.
    fn test_initialization_add_stratis(paths: &[&Path]) {
        assert!(paths.len() > 1);
        let (paths1, paths2) = paths.split_at(paths.len() / 2);

        let uuid = PoolUuid::new_v4();
        let uuid2 = PoolUuid::new_v4();

        let bd_mgr = BlockDevMgr::initialize(
            uuid,
            get_devices(paths1).unwrap(),
            MDADataSize::default(),
            None,
        )
        .unwrap();
        cmd::udev_settle().unwrap();

        assert_matches!(get_devices(paths1), Err(_));

        assert!(ProcessedPathInfos::try_from(paths1)
            .unwrap()
            .unpack()
            .0
            .partition(uuid2)
            .0
            .is_empty());

        assert!(!ProcessedPathInfos::try_from(paths1)
            .unwrap()
            .unpack()
            .0
            .partition(uuid)
            .0
            .is_empty());

        BlockDevMgr::initialize(
            uuid,
            get_devices(paths2).unwrap(),
            MDADataSize::default(),
            None,
        )
        .unwrap();

        cmd::udev_settle().unwrap();

        assert!(!ProcessedPathInfos::try_from(paths2)
            .unwrap()
            .unpack()
            .0
            .partition(uuid)
            .0
            .is_empty());

        bd_mgr.invariant()
    }

    #[test]
    fn loop_test_initialization_stratis() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Range(2, 3, None),
            test_initialization_add_stratis,
        );
    }

    #[test]
    fn real_test_initialization_stratis() {
        real::test_with_spec(
            &real::DeviceLimits::AtLeast(2, None, None),
            test_initialization_add_stratis,
        );
    }

    fn test_clevis_initialize(paths: &[&Path]) {
        unshare_namespace().unwrap();
        let _memfs = MemoryFilesystem::new().unwrap();
        let pool_uuid = PoolUuid::new_v4();
        let mut mgr = BlockDevMgr::initialize(
            pool_uuid,
            get_devices(paths).unwrap(),
            MDADataSize::default(),
            Some(&EncryptionInfo::ClevisInfo((
                "tang".to_string(),
                json!({"url": env::var("TANG_URL").unwrap(), "stratis:tang:trust_url": true}),
            ))),
        )
        .unwrap();
        cmd::udev_settle().unwrap();

        matches!(
            mgr.bind_clevis(
                "tang",
                &json!({"url": env::var("TANG_URL").unwrap(), "stratis:tang:trust_url": true})
            ),
            Ok(false)
        );
    }

    #[test]
    fn clevis_real_test_initialize() {
        real::test_with_spec(
            &real::DeviceLimits::AtLeast(2, None, None),
            test_clevis_initialize,
        );
    }

    #[test]
    #[should_panic]
    fn clevis_real_should_fail_test_initialize() {
        real::test_with_spec(
            &real::DeviceLimits::AtLeast(2, None, None),
            test_clevis_initialize,
        );
    }

    #[test]
    fn clevis_loop_test_initialize() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Range(2, 4, None),
            test_clevis_initialize,
        );
    }

    #[test]
    #[should_panic]
    fn clevis_loop_should_fail_test_initialize() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Range(2, 4, None),
            test_clevis_initialize,
        );
    }

    fn test_clevis_both_initialize(paths: &[&Path]) {
        fn test_both(paths: &[&Path], key_desc: &KeyDescription) -> Result<(), Box<dyn Error>> {
            unshare_namespace()?;
            let _memfs = MemoryFilesystem::new().unwrap();
            let pool_uuid = PoolUuid::new_v4();
            let mut mgr = BlockDevMgr::initialize(
                pool_uuid,
                get_devices(paths).unwrap(),
                MDADataSize::default(),
                Some(&EncryptionInfo::Both(
                    key_desc.clone(),
                    (
                        "tang".to_string(),
                        json!({"url": env::var("TANG_URL")?, "stratis:tang:trust_url": true}),
                    ),
                )),
            )?;
            cmd::udev_settle()?;

            if mgr.bind_clevis(
                "tang",
                &json!({"url": env::var("TANG_URL")?, "stratis:tang:trust_url": true}),
            )? {
                return Err(Box::new(StratisError::Msg(
                    "Clevis bind idempotence test failed".to_string(),
                )));
            }
            if mgr.bind_keyring(key_desc)? {
                return Err(Box::new(StratisError::Msg(
                    "Keyring bind idempotence test failed".to_string(),
                )));
            }

            if !(mgr.unbind_clevis()?) {
                return Err(Box::new(StratisError::Msg(
                    "Clevis unbind test failed".to_string(),
                )));
            }
            if mgr.unbind_clevis()? {
                return Err(Box::new(StratisError::Msg(
                    "Clevis unbind idempotence test failed".to_string(),
                )));
            }
            if mgr.unbind_keyring().is_ok() {
                return Err(Box::new(StratisError::Msg(
                    "Keyring unbind check test failed".to_string(),
                )));
            }

            if !(mgr.bind_clevis(
                "tang",
                &json!({"url": env::var("TANG_URL")?, "stratis:tang:trust_url": true}),
            )?) {
                return Err(Box::new(StratisError::Msg(
                    "Clevis bind test failed".to_string(),
                )));
            }
            if !(mgr.unbind_keyring()?) {
                return Err(Box::new(StratisError::Msg(
                    "Keyring unbind test failed".to_string(),
                )));
            }
            if mgr.unbind_keyring()? {
                return Err(Box::new(StratisError::Msg(
                    "Keyring unbind idempotence test failed".to_string(),
                )));
            }
            if mgr.unbind_clevis().is_ok() {
                return Err(Box::new(StratisError::Msg(
                    "Clevis unbind check test failed".to_string(),
                )));
            }

            if !(mgr.bind_keyring(key_desc)?) {
                return Err(Box::new(StratisError::Msg(
                    "Keyring bind test failed".to_string(),
                )));
            }

            Ok(())
        }

        crypt::insert_and_cleanup_key(paths, test_both);
    }

    #[test]
    fn clevis_real_test_both_initialize() {
        real::test_with_spec(
            &real::DeviceLimits::AtLeast(2, None, None),
            test_clevis_both_initialize,
        );
    }

    #[test]
    #[should_panic]
    fn clevis_real_should_fail_test_both_initialize() {
        real::test_with_spec(
            &real::DeviceLimits::AtLeast(2, None, None),
            test_clevis_both_initialize,
        );
    }

    #[test]
    fn clevis_loop_test_both_initialize() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Range(2, 4, None),
            test_clevis_both_initialize,
        );
    }

    #[test]
    #[should_panic]
    fn clevis_loop_should_fail_test_both_initialize() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Range(2, 4, None),
            test_clevis_both_initialize,
        );
    }
}

<MSG> Merge pull request #793 from mulkieran/master-blockdevs-3

Handle change in blockdev size on setup
<DFF> @@ -433,13 +433,11 @@ fn initialize(pool_uuid: PoolUuid,
 
     let mut bds: Vec<StratBlockDev> = Vec::new();
     for (dev, (devnode, dev_size, mut f)) in add_devs {
-        let dev_size = dev_size.sectors();
-
         let bda = BDA::initialize(&mut f,
                                   pool_uuid,
                                   Uuid::new_v4(),
                                   mda_size,
-                                  dev_size,
+                                  dev_size.sectors(),
                                   Utc::now().timestamp() as u64);
         if let Ok(bda) = bda {
             let hw_id = match hw_lookup(devnode) {
@@ -451,8 +449,8 @@ fn initialize(pool_uuid: PoolUuid,
             // The dev_size is at least MIN_DEV_SIZE, but the size of the
             // metadata is not really bounded from above.
             let blockdev =
-                StratBlockDev::new(dev, devnode.to_owned(), dev_size, bda, &[], None, hw_id)
-                    .expect("bda.size() < dev_size and no user facing segments allocated");
+                StratBlockDev::new(dev, devnode.to_owned(), bda, &[], None, hw_id)
+                    .expect("bda.size() == dev_size; only allocating space for metadata");
             bds.push(blockdev);
         } else {
             // TODO: check the return values and update state machine on failure

{'commit': 'diff --git a/src/engine/strat_engine/backstore/backstore.rs b/src/engine/strat_engine/backstore/backstore.rs\nindex 93770938..efd954d0 100644\n--- a/src/engine/strat_engine/backstore/backstore.rs\n+++ b/src/engine/strat_engine/backstore/backstore.rs\n@@ -22,20 +22,20 @@ use super::blockdevmgr::{BlkDevSegment, BlockDevMgr, Segment, coalesce_blkdevseg\n use super::setup::get_blockdevs;\n \n /// Handles the lowest level, base layer of this tier.\n-/// The dm_device organizes all block devs into a single linear allocation pool.\n-/// All available space on the blockdevs is allocated to dm_device.\n-/// This structure can allocate additional space to the upper layer, but it\n-/// cannot accept returned space. When it is extended to be able to accept\n-/// returned space the allocation algorithm will have to be revised.\n+/// The dm_device organizes all block devs into a single linear allocation\n+/// pool. This structure can allocate additional space to the upper layer,\n+/// but it cannot accept returned space. When it is extended to be able to\n+/// accept returned space the allocation algorithm will have to be revised.\n+/// All available sectors on blockdevs in the manager are allocated to\n+/// dm_device.\n #[derive(Debug)]\n struct DataTier {\n     /// Manages the individual block devices\n+    /// it is always the case block_mgr.avail_space() == 0.\n     block_mgr: BlockDevMgr,\n-    /// The device mapper device which aggregates all block_mgr\'s devices\n+    /// The device mapper device which aggregates block_mgr\'s devices\n     dm_device: LinearDev,\n     /// The list of segments granted by block_mgr and used by dm_device\n-    /// It is always the case that block_mgr.avail_space() == 0, i.e., all\n-    /// available space in block_mgr is allocated to the dm_device.\n     segments: Vec<BlkDevSegment>,\n     /// The position from which requested space is allocated\n     next: Sectors,\n@@ -49,10 +49,16 @@ impl DataTier {\n     /// might have resulted from this.\n     /// WARNING: metadata changing event\n     pub fn setup(dm: &DM,\n-                 mut block_mgr: BlockDevMgr,\n+                 block_mgr: BlockDevMgr,\n                  segments: &[(DevUuid, Sectors, Sectors)],\n                  next: Sectors)\n                  -> EngineResult<DataTier> {\n+        if block_mgr.avail_space() != Sectors(0) {\n+            let err_msg = format!("{} unallocated to device; probable metadata corruption",\n+                                  block_mgr.avail_space());\n+            return Err(EngineError::Engine(ErrorEnum::Error, err_msg));\n+        }\n+\n         let uuid_to_devno = block_mgr.uuid_to_devno();\n         let mapper = |triple: &(DevUuid, Sectors, Sectors)| -> EngineResult<BlkDevSegment> {\n             let device = uuid_to_devno(triple.0)\n@@ -63,33 +69,16 @@ impl DataTier {\n                             })?;\n             Ok(BlkDevSegment::new(triple.0, Segment::new(device, triple.1, triple.2)))\n         };\n-        let mut segments = segments\n+        let segments = segments\n             .iter()\n             .map(&mapper)\n             .collect::<EngineResult<Vec<_>>>()?;\n \n-        let mut ld = LinearDev::setup(dm,\n-                                      &format_backstore_name(block_mgr.pool_uuid(),\n-                                                             CacheRole::OriginSub),\n-                                      None,\n-                                      map_to_dm(&segments))?;\n-\n-        let avail_space = block_mgr.avail_space();\n-        let additional_segments = block_mgr\n-            .alloc_space(&[avail_space])\n-            .expect("asked for exactly the space available, must get")\n-            .iter()\n-            .flat_map(|s| s.iter())\n-            .cloned()\n-            .collect::<Vec<_>>();\n-\n-        // Add the new segments only after setting up the linear dev.\n-        // Otherwise the new table will differ from the old and setup of the\n-        // dev will fail.\n-        if !additional_segments.is_empty() {\n-            segments.extend(additional_segments);\n-            ld.set_table(dm, map_to_dm(&segments))?;\n-        }\n+        let ld = LinearDev::setup(dm,\n+                                  &format_backstore_name(block_mgr.pool_uuid(),\n+                                                         CacheRole::OriginSub),\n+                                  None,\n+                                  map_to_dm(&segments))?;\n \n         Ok(DataTier {\n                block_mgr,\ndiff --git a/src/engine/strat_engine/backstore/blockdev.rs b/src/engine/strat_engine/backstore/blockdev.rs\nindex efe602d3..8f3bd290 100644\n--- a/src/engine/strat_engine/backstore/blockdev.rs\n+++ b/src/engine/strat_engine/backstore/blockdev.rs\n@@ -36,18 +36,19 @@ impl StratBlockDev {\n     /// Allocate space for the Stratis metadata on the device.\n     /// - dev: the device, identified by number\n     /// - devnode: the device node\n-    /// - dev_size: the total size of the device\n     /// - bda: the device\'s BDA\n     /// - other_segments: segments claimed for non-Stratis metadata use\n     /// - user_info: user settable identifying information\n     /// - hardware_info: identifying information in the hardware\n     /// Returns an error if it is impossible to allocate all segments on the\n-    /// device. This can happen even if upper_segments is empty,\n-    /// if the device size is less than the size required for the Stratis\n-    /// metadata.\n+    /// device.\n+    /// NOTE: It is possible that the actual device size is greater than\n+    /// the recorded device size. In that case, the additional space available\n+    /// on the device is simply invisible to the blockdev. Consequently, it\n+    /// is invisible to the engine, and is not part of the total size value\n+    /// reported on the D-Bus.\n     pub fn new(dev: Device,\n                devnode: PathBuf,\n-               dev_size: Sectors,\n                bda: BDA,\n                upper_segments: &[(Sectors, Sectors)],\n                user_info: Option<String>,\n@@ -55,10 +56,7 @@ impl StratBlockDev {\n                -> EngineResult<StratBlockDev> {\n         let mut segments = vec![(Sectors(0), bda.size())];\n         segments.extend(upper_segments);\n-        let allocator = RangeAllocator::new(dev_size, &segments)?;\n-\n-        assert!(bda.size() <= dev_size,\n-                "Otherwise the segments for the metadata could not have been allocated");\n+        let allocator = RangeAllocator::new(bda.dev_size(), &segments)?;\n \n         Ok(StratBlockDev {\n                dev,\n@@ -110,16 +108,11 @@ impl StratBlockDev {\n     }\n \n     // ALL SIZE METHODS\n-\n-    /// The size of the device as recorded in the metadata.\n-    #[allow(dead_code)]\n-    pub fn recorded_size(&self) -> Sectors {\n-        self.bda.dev_size()\n-    }\n-\n     /// The actual size of the device now.\n     pub fn current_capacity(&self) -> Sectors {\n-        self.used.capacity()\n+        let size = self.used.capacity();\n+        assert_eq!(self.bda.dev_size(), size);\n+        size\n     }\n \n     /// The number of Sectors on this device used by Stratis for metadata\ndiff --git a/src/engine/strat_engine/backstore/blockdevmgr.rs b/src/engine/strat_engine/backstore/blockdevmgr.rs\nindex 27bf1046..62afb1d2 100644\n--- a/src/engine/strat_engine/backstore/blockdevmgr.rs\n+++ b/src/engine/strat_engine/backstore/blockdevmgr.rs\n@@ -433,13 +433,11 @@ fn initialize(pool_uuid: PoolUuid,\n \n     let mut bds: Vec<StratBlockDev> = Vec::new();\n     for (dev, (devnode, dev_size, mut f)) in add_devs {\n-        let dev_size = dev_size.sectors();\n-\n         let bda = BDA::initialize(&mut f,\n                                   pool_uuid,\n                                   Uuid::new_v4(),\n                                   mda_size,\n-                                  dev_size,\n+                                  dev_size.sectors(),\n                                   Utc::now().timestamp() as u64);\n         if let Ok(bda) = bda {\n             let hw_id = match hw_lookup(devnode) {\n@@ -451,8 +449,8 @@ fn initialize(pool_uuid: PoolUuid,\n             // The dev_size is at least MIN_DEV_SIZE, but the size of the\n             // metadata is not really bounded from above.\n             let blockdev =\n-                StratBlockDev::new(dev, devnode.to_owned(), dev_size, bda, &[], None, hw_id)\n-                    .expect("bda.size() < dev_size and no user facing segments allocated");\n+                StratBlockDev::new(dev, devnode.to_owned(), bda, &[], None, hw_id)\n+                    .expect("bda.size() == dev_size; only allocating space for metadata");\n             bds.push(blockdev);\n         } else {\n             // TODO: check the return values and update state machine on failure\ndiff --git a/src/engine/strat_engine/backstore/setup.rs b/src/engine/strat_engine/backstore/setup.rs\nindex 6830f8b0..2282a126 100644\n--- a/src/engine/strat_engine/backstore/setup.rs\n+++ b/src/engine/strat_engine/backstore/setup.rs\n@@ -190,6 +190,14 @@ pub fn get_blockdevs(pool_uuid: PoolUuid,\n                 let actual_size = blkdev_size(&OpenOptions::new().read(true).open(devnode)?)?\n                     .sectors();\n \n+                if actual_size < bda.dev_size() {\n+                    let err_msg = format!("actual blockdev size ({}) < recorded size ({})",\n+                                          actual_size,\n+                                          bda.dev_size());\n+\n+                    return Err(EngineError::Engine(ErrorEnum::Error, err_msg));\n+                }\n+\n                 let bd_save = backstore_save\n                     .block_devs\n                     .iter()\n@@ -200,14 +208,13 @@ pub fn get_blockdevs(pool_uuid: PoolUuid,\n                                     EngineError::Engine(ErrorEnum::NotFound, err_msg)\n                                 })?;\n \n-                // If the size of the device has changed and is less,\n-                // then it is possible that the segments previously allocated\n-                // for this blockdev no longer exist. If that is the case,\n-                // StratBlockDev::new will return an error.\n+                // This should always succeed since the actual size is at\n+                // least the recorded size, so all segments should be\n+                // available to be allocated. If this fails, the most likely\n+                // conclusion is metadata corruption.\n                 let segments = segment_table.get(&bda.dev_uuid());\n                 blockdevs.push(StratBlockDev::new(*device,\n                                                   devnode.to_owned(),\n-                                                  actual_size,\n                                                   bda,\n                                                   segments.unwrap_or(&vec![]),\n                                                   bd_save.user_info.clone(),\n', 'commit_subject': 'Merge pull request #793 from mulkieran/master-blockdevs-3', 'repo_name': 'stratis-storage/stratisd', 'committer': {'name': 'GitHub', 'email': '1505422b2465e9a84f6fdfaa161078890c593f06@github.com', 'time_sec': '1519418424', 'tz_offset': '-300', 'date': {'seconds': '1519418424'}}, 'commit_tree': '90bf989989db89ef29dd12e657cf57a4ef7d0140', 'commit_message': 'Merge pull request #793 from mulkieran/master-blockdevs-3\n\nHandle change in blockdev size on setup', 'language_name': 'Rust', 'license': 'mpl-2.0', 'hunks': '@@ -433,13 +433,11 @@ fn initialize(pool_uuid: PoolUuid,\n \n     let mut bds: Vec<StratBlockDev> = Vec::new();\n     for (dev, (devnode, dev_size, mut f)) in add_devs {\n-        let dev_size = dev_size.sectors();\n-\n         let bda = BDA::initialize(&mut f,\n                                   pool_uuid,\n                                   Uuid::new_v4(),\n                                   mda_size,\n-                                  dev_size,\n+                                  dev_size.sectors(),\n                                   Utc::now().timestamp() as u64);\n         if let Ok(bda) = bda {\n             let hw_id = match hw_lookup(devnode) {\n@@ -451,8 +449,8 @@ fn initialize(pool_uuid: PoolUuid,\n             // The dev_size is at least MIN_DEV_SIZE, but the size of the\n             // metadata is not really bounded from above.\n             let blockdev =\n-                StratBlockDev::new(dev, devnode.to_owned(), dev_size, bda, &[], None, hw_id)\n-                    .expect("bda.size() < dev_size and no user facing segments allocated");\n+                StratBlockDev::new(dev, devnode.to_owned(), bda, &[], None, hw_id)\n+                    .expect("bda.size() == dev_size; only allocating space for metadata");\n             bds.push(blockdev);\n         } else {\n             // TODO: check the return values and update state machine on failure\n', 'addition_count': 3, 'deletion_count': 5, 'src_file': 'a/src/engine/strat_engine/backstore/blockdevmgr.rs', 'tgt_file': 'b/src/engine/strat_engine/backstore/blockdevmgr.rs', 'file_extension': '.rs', 'before_file': '// This Source Code Form is subject to the terms of the Mozilla Public\n// License, v. 2.0. If a copy of the MPL was not distributed with this\n// file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n// Code to handle a collection of block devices.\n\n#[cfg(test)]\nuse std::collections::HashSet;\n\nuse std::{collections::HashMap, fs, path::PathBuf};\n\nuse chrono::{DateTime, Duration, Utc};\nuse rand::{seq::IteratorRandom, thread_rng};\nuse serde_json::Value;\nuse tempfile::TempDir;\n\nuse devicemapper::{Bytes, Device, LinearDevTargetParams, LinearTargetParams, Sectors, TargetLine};\n\nuse crate::{\n    engine::{\n        shared::gather_encryption_info,\n        strat_engine::{\n            backstore::{\n                blockdev::StratBlockDev,\n                crypt::{\n                    back_up_luks_header, interpret_clevis_config, restore_luks_header,\n                    CryptActivationHandle,\n                },\n                devices::{initialize_devices, wipe_blockdevs, UnownedDevices},\n                range_alloc::PerDevSegments,\n                transaction::RequestTransaction,\n            },\n            metadata::MDADataSize,\n            serde_structs::{BaseBlockDevSave, BaseDevSave, Recordable},\n        },\n        types::{\n            ActionAvailability, DevUuid, EncryptionInfo, KeyDescription, PoolEncryptionInfo,\n            PoolUuid,\n        },\n    },\n    stratis::{StratisError, StratisResult},\n};\n\nconst MAX_NUM_TO_WRITE: usize = 10;\n\n/// struct to represent a continuous set of sectors on a disk\n#[derive(Debug, Clone)]\npub struct Segment {\n    /// The offset into the device where this segment starts.\n    pub(super) start: Sectors,\n    /// The length of the segment.\n    pub(super) length: Sectors,\n    /// The device the segment is within.\n    pub(super) device: Device,\n}\n\nimpl Segment {\n    /// Create a new Segment with given attributes\n    pub fn new(device: Device, start: Sectors, length: Sectors) -> Segment {\n        Segment {\n            start,\n            length,\n            device,\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct BlkDevSegment {\n    pub(super) uuid: DevUuid,\n    pub(super) segment: Segment,\n}\n\nimpl BlkDevSegment {\n    pub fn new(uuid: DevUuid, segment: Segment) -> BlkDevSegment {\n        BlkDevSegment { uuid, segment }\n    }\n\n    pub fn to_segment(&self) -> Segment {\n        self.segment.clone()\n    }\n}\n\nimpl Recordable<Vec<BaseDevSave>> for Vec<BlkDevSegment> {\n    fn record(&self) -> Vec<BaseDevSave> {\n        self.iter()\n            .map(|bseg| BaseDevSave {\n                parent: bseg.uuid,\n                start: bseg.segment.start,\n                length: bseg.segment.length,\n            })\n            .collect::<Vec<_>>()\n    }\n}\n\n/// Build a linear dev target table from BlkDevSegments. This is useful for\n/// calls to the devicemapper library.\npub fn map_to_dm(bsegs: &[BlkDevSegment]) -> Vec<TargetLine<LinearDevTargetParams>> {\n    let mut table = Vec::new();\n    let mut logical_start_offset = Sectors(0);\n\n    let segments = bsegs\n        .iter()\n        .map(|bseg| bseg.to_segment())\n        .collect::<Vec<_>>();\n    for segment in segments {\n        let (physical_start_offset, length) = (segment.start, segment.length);\n        let params = LinearTargetParams::new(segment.device, physical_start_offset);\n        let line = TargetLine::new(\n            logical_start_offset,\n            length,\n            LinearDevTargetParams::Linear(params),\n        );\n        table.push(line);\n        logical_start_offset += length;\n    }\n\n    table\n}\n\n#[derive(Debug)]\npub struct BlockDevMgr {\n    /// All the block devices that belong to this block dev manager.\n    block_devs: Vec<StratBlockDev>,\n    /// The most recent time that variable length metadata was saved to the\n    /// devices managed by this block dev manager.\n    last_update_time: Option<DateTime<Utc>>,\n}\n\nimpl BlockDevMgr {\n    /// Make a struct that represents an existing BlockDevMgr.\n    pub fn new(\n        block_devs: Vec<StratBlockDev>,\n        last_update_time: Option<DateTime<Utc>>,\n    ) -> BlockDevMgr {\n        BlockDevMgr {\n            block_devs,\n            last_update_time,\n        }\n    }\n\n    /// Initialize a new StratBlockDevMgr with specified pool and devices.\n    pub fn initialize(\n        pool_uuid: PoolUuid,\n        devices: UnownedDevices,\n        mda_data_size: MDADataSize,\n        encryption_info: Option<&EncryptionInfo>,\n    ) -> StratisResult<BlockDevMgr> {\n        Ok(BlockDevMgr::new(\n            initialize_devices(devices, pool_uuid, mda_data_size, encryption_info)?,\n            None,\n        ))\n    }\n\n    /// Get a hashmap that maps UUIDs to Devices.\n    pub fn uuid_to_devno(&self) -> HashMap<DevUuid, Device> {\n        self.block_devs\n            .iter()\n            .map(|bd| (bd.uuid(), *bd.device()))\n            .collect()\n    }\n\n    /// Check that the registered key description and Clevis information for these\n    /// block devices can unlock at least one of the existing block devices registered.\n    /// Precondition: self.block_devs must have at least one device.\n    pub fn can_unlock(&self, try_unlock_keyring: bool, try_unlock_clevis: bool) -> bool {\n        CryptActivationHandle::can_unlock(\n            self.block_devs\n                .get(0)\n                .expect("Must have at least one blockdev")\n                .physical_path(),\n            try_unlock_keyring,\n            try_unlock_clevis,\n        )\n    }\n\n    /// Add paths to self.\n    /// Return the uuids of all blockdevs corresponding to paths that were\n    /// added.\n    pub fn add(\n        &mut self,\n        pool_uuid: PoolUuid,\n        devices: UnownedDevices,\n    ) -> StratisResult<Vec<DevUuid>> {\n        let this_pool_uuid = self.block_devs.get(0).map(|bd| bd.pool_uuid());\n        if this_pool_uuid.is_some() && this_pool_uuid != Some(pool_uuid) {\n            return Err(StratisError::Msg(\n                format!("block devices being managed have pool UUID {} but new devices are to be added with pool UUID {}",\n                        this_pool_uuid.expect("guarded by if-expression"),\n                        pool_uuid)\n            ));\n        }\n\n        let encryption_info = pool_enc_to_enc!(self.encryption_info());\n        if let Some(ref ei) = encryption_info {\n            if !self.can_unlock(ei.key_description().is_some(), ei.clevis_info().is_some()) {\n                return Err(StratisError::Msg(\n                    "Neither the key in the kernel keyring nor Clevis could be used to perform encryption operations on the devices in the pool; check that either the appropriate key in the keyring is set or that the Clevis key storage method is available".to_string(),\n                ));\n            }\n        }\n\n        // FIXME: This is a bug. If new devices are added to a pool, and the\n        // variable length metadata requires more than the minimum allocated,\n        // then the necessary amount must be provided or the data can not be\n        // saved.\n        let bds = initialize_devices(\n            devices,\n            pool_uuid,\n            MDADataSize::default(),\n            encryption_info.as_ref(),\n        )?;\n        let bdev_uuids = bds.iter().map(|bd| bd.uuid()).collect();\n        self.block_devs.extend(bds);\n        Ok(bdev_uuids)\n    }\n\n    pub fn destroy_all(&mut self) -> StratisResult<()> {\n        wipe_blockdevs(&mut self.block_devs)\n    }\n\n    /// Remove the specified block devs and erase their metadata.\n    ///\n    /// Precondition: It is the responsibility of the caller to ensure that\n    /// none of the blockdevs are in use, that is, have had any space allocated\n    /// from them for upper layers.\n    ///\n    /// If a specified blockdev is not found, returns an error and does nothing.\n    ///\n    /// NOTE: This method traverses the block_devs Vec from the rear to the\n    /// front, looking for blockdevs to remove. This is algorithmically\n    /// inefficient, unless it is assumed that the blockdevs specified are very\n    /// near the end of the Vec, which is expected to be the case. In that case,\n    /// the algorithm is O(n).\n    pub(super) fn remove_blockdevs(&mut self, uuids: &[DevUuid]) -> StratisResult<()> {\n        let mut removed = Vec::new();\n        for uuid in uuids {\n            let mut found = false;\n            let blockdevs_last_index = self.block_devs.len() - 1;\n            for i in 0..blockdevs_last_index {\n                let index = blockdevs_last_index - i;\n                if self.block_devs[index].uuid() == *uuid {\n                    removed.push(self.block_devs.swap_remove(index));\n                    found = true;\n                    break;\n                }\n            }\n            if !found {\n                return Err(StratisError::Msg(format!(\n                    "Blockdev corresponding to UUID: {} not found.",\n                    uuid\n                )));\n            }\n        }\n        wipe_blockdevs(&mut removed)?;\n        Ok(())\n    }\n\n    /// Allocate space according to sizes vector request.\n    /// Return the segments allocated for each request, or None if it was\n    /// not possible to satisfy the request.\n    /// This method is atomic, it either allocates all requested or allocates\n    /// nothing.\n    pub fn request_space(&self, sizes: &[Sectors]) -> StratisResult<Option<RequestTransaction>> {\n        let mut transaction = RequestTransaction::default();\n\n        let total_needed: Sectors = sizes.iter().cloned().sum();\n        if self.avail_space() < total_needed {\n            return Ok(None);\n        }\n\n        for (idx, &needed) in sizes.iter().enumerate() {\n            let mut alloc = Sectors(0);\n            // TODO: Consider greater efficiency for allocation generally.\n            // Over time, the blockdevs at the start will be exhausted. It\n            // might be a good idea to keep an auxiliary structure, so that\n            // only blockdevs with some space left to allocate are accessed.\n            // In the context of this major inefficiency that ensues over time\n            // the obvious but more minor inefficiency of this inner loop is\n            // not worth worrying about.\n            for bd in &self.block_devs {\n                if alloc == needed {\n                    break;\n                }\n\n                let r_segs = bd.request_space(needed - alloc, &transaction)?;\n                for (&start, &length) in r_segs.iter() {\n                    transaction.add_bd_seg_req(\n                        idx,\n                        BlkDevSegment::new(bd.uuid(), Segment::new(*bd.device(), start, length)),\n                    );\n                }\n                alloc += r_segs.sum();\n            }\n            assert_eq!(alloc, needed);\n        }\n\n        Ok(Some(transaction))\n    }\n\n    /// Commit the allocations calculated by the request_space() method.\n    ///\n    /// This method converts the block device segments into the necessary data\n    /// structure and dispatches them to the corresponding block devices to\n    /// update the internal records of allocated space.\n    pub fn commit_space(&mut self, mut transaction: RequestTransaction) -> StratisResult<()> {\n        let mut segs = transaction.drain_blockdevmgr().try_fold(\n            HashMap::<DevUuid, PerDevSegments>::new(),\n            |mut map, seg| -> StratisResult<_> {\n                if let Some(segs) = map.get_mut(&seg.uuid) {\n                    segs.insert(&(seg.segment.start, seg.segment.length))?;\n                } else {\n                    let mut segs = PerDevSegments::new(\n                        self.block_devs\n                            .iter()\n                            .find(|bd| bd.uuid() == seg.uuid)\n                            .expect(\n                                "Block dev was determined to be present during allocation request",\n                            )\n                            .total_size()\n                            .sectors(),\n                    );\n                    segs.insert(&(seg.segment.start, seg.segment.length))?;\n                    map.insert(seg.uuid, segs);\n                }\n\n                Ok(map)\n            },\n        )?;\n\n        for (uuid, bd) in self.blockdevs_mut() {\n            if let Some(segs) = segs.remove(&uuid) {\n                bd.commit_space(segs);\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Write the given data to all blockdevs marking with current time.\n    /// Return an error if data was not written to any blockdev.\n    /// Omit blockdevs which do not have sufficient space in BDA to accommodate\n    /// metadata. If current time is not more recent than previously written\n    /// time, use a time that is one nanosecond greater than that previously\n    /// written. Randomly select no more than MAX_NUM_TO_WRITE blockdevs to\n    /// write to.\n    pub fn save_state(&mut self, metadata: &[u8]) -> StratisResult<()> {\n        let current_time = Utc::now();\n        let stamp_time = if Some(current_time) <= self.last_update_time {\n            self.last_update_time\n                .expect("self.last_update_time >= Some(current_time")\n                .checked_add_signed(Duration::nanoseconds(1))\n                .expect("self.last_update_time << maximum representable DateTime")\n        } else {\n            current_time\n        };\n\n        let data_size = Bytes::from(metadata.len());\n        let candidates = self\n            .block_devs\n            .iter_mut()\n            .filter(|b| b.max_metadata_size().bytes() >= data_size);\n\n        // TODO: consider making selection not entirely random, i.e, ensuring\n        // distribution of metadata over different paths.\n        let saved = candidates\n            .choose_multiple(&mut thread_rng(), MAX_NUM_TO_WRITE)\n            .iter_mut()\n            .fold(false, |acc, b| {\n                acc | b.save_state(&stamp_time, metadata).is_ok()\n            });\n\n        if saved {\n            self.last_update_time = Some(stamp_time);\n            Ok(())\n        } else {\n            let err_msg = "Failed to save metadata to even one device in pool";\n            Err(StratisError::Msg(err_msg.into()))\n        }\n    }\n\n    /// Get references to managed blockdevs.\n    pub fn blockdevs(&self) -> Vec<(DevUuid, &StratBlockDev)> {\n        self.block_devs.iter().map(|bd| (bd.uuid(), bd)).collect()\n    }\n\n    pub fn blockdevs_mut(&mut self) -> Vec<(DevUuid, &mut StratBlockDev)> {\n        self.block_devs\n            .iter_mut()\n            .map(|bd| (bd.uuid(), bd as &mut StratBlockDev))\n            .collect()\n    }\n\n    pub fn get_blockdev_by_uuid(&self, uuid: DevUuid) -> Option<&StratBlockDev> {\n        self.block_devs.iter().find(|bd| bd.uuid() == uuid)\n    }\n\n    pub fn get_mut_blockdev_by_uuid(&mut self, uuid: DevUuid) -> Option<&mut StratBlockDev> {\n        self.block_devs.iter_mut().find(|bd| bd.uuid() == uuid)\n    }\n\n    // SIZE methods\n\n    /// The number of sectors not allocated for any purpose.\n    pub fn avail_space(&self) -> Sectors {\n        self.block_devs.iter().map(|bd| bd.available()).sum()\n    }\n\n    /// The current size of all the blockdevs.\n    /// self.size() > self.avail_space() because some sectors are certainly\n    /// allocated for Stratis metadata\n    pub fn size(&self) -> Sectors {\n        self.block_devs\n            .iter()\n            .map(|b| b.total_size().sectors())\n            .sum()\n    }\n\n    /// The number of sectors given over to Stratis metadata\n    /// self.allocated_size() - self.metadata_size() >= self.avail_space()\n    pub fn metadata_size(&self) -> Sectors {\n        self.block_devs\n            .iter()\n            .map(|bd| bd.metadata_size().sectors())\n            .sum()\n    }\n\n    /// Get the encryption information for a whole pool.\n    pub fn encryption_info(&self) -> Option<PoolEncryptionInfo> {\n        gather_encryption_info(\n            self.block_devs.len(),\n            self.block_devs.iter().map(|bd| bd.encryption_info()),\n\n    let mut bds: Vec<StratBlockDev> = Vec::new();\n    for (dev, (devnode, dev_size, mut f)) in add_devs {\n        let dev_size = dev_size.sectors();\n\n        let bda = BDA::initialize(&mut f,\n                                  pool_uuid,\n                                  Uuid::new_v4(),\n                                  mda_size,\n                                  dev_size,\n                                  Utc::now().timestamp() as u64);\n        if let Ok(bda) = bda {\n            let hw_id = match hw_lookup(devnode) {\n            .block_devs\n            .iter()\n            .map(|bd| bd.pool_uuid())\n            .collect::<HashSet<_>>();\n        assert!(pool_uuids.len() == 1);\n            // The dev_size is at least MIN_DEV_SIZE, but the size of the\n            // metadata is not really bounded from above.\n            let blockdev =\n                StratBlockDev::new(dev, devnode.to_owned(), dev_size, bda, &[], None, hw_id)\n                    .expect("bda.size() < dev_size and no user facing segments allocated");\n            bds.push(blockdev);\n        } else {\n            // TODO: check the return values and update state machine on failure\n        } else {\n            assert_eq!(encryption_infos.len(), self.block_devs.len());\n\n            let info_set = encryption_infos.iter().collect::<HashSet<_>>();\n            assert!(info_set.len() == 1);\n        }\n\n        for bd in self.block_devs.iter() {\n            bd.invariant();\n        }\n    }\n\n    /// Bind all devices in the given blockdev manager using the given clevis\n    /// configuration.\n    ///\n    /// * Returns Ok(true) if the binding was performed.\n    /// * Returns Ok(false) if the binding had already been previously performed and\n    /// nothing was changed.\n    /// * Returns Err(_) if an inconsistency was found in the metadata across pools\n    /// or binding failed.\n    pub fn bind_clevis(&mut self, pin: &str, clevis_info: &Value) -> StratisResult<bool> {\n        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {\n            Some(ei) => ei,\n            None => {\n                return Err(StratisError::Msg(\n                    "Requested pool does not appear to be encrypted".to_string(),\n                ));\n            }\n        };\n\n        let mut parsed_config = clevis_info.clone();\n        let yes = interpret_clevis_config(pin, &mut parsed_config)?;\n\n        if let Some((ref existing_pin, ref existing_info)) = encryption_info.clevis_info() {\n            // Ignore thumbprint if stratis:tang:trust_url is set in the clevis_info\n            // config.\n            let mut config_to_check = existing_info.clone();\n            if yes {\n                if let Value::Object(ref mut ei) = config_to_check {\n                    ei.remove("thp");\n                }\n            }\n\n            if (existing_pin.as_str(), &config_to_check) == (pin, &parsed_config)\n                && self.can_unlock(false, true)\n            {\n                Ok(false)\n            } else {\n                Err(StratisError::Msg(format!(\n                    "Block devices have already been bound with pin {} and config {}; \\\n                        requested pin {} and config {} can\'t be applied",\n                    existing_pin, existing_info, pin, parsed_config,\n                )))\n            }\n        } else {\n            operation_loop(\n                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),\n                |blockdev| blockdev.bind_clevis(pin, clevis_info),\n            )?;\n            Ok(true)\n        }\n    }\n\n    /// Unbind all devices in the given blockdev manager from clevis.\n    ///\n    /// * Returns Ok(true) if the unbinding was performed.\n    /// * Returns Ok(false) if the unbinding had already been previously performed and\n    /// nothing was changed.\n    /// * Returns Err(_) if an inconsistency was found in the metadata across pools\n    /// or unbinding failed.\n    pub fn unbind_clevis(&mut self) -> StratisResult<bool> {\n        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {\n            Some(ei) => ei,\n            None => {\n                return Err(StratisError::Msg(\n                    "Requested pool does not appear to be encrypted".to_string(),\n                ));\n            }\n        };\n\n        if encryption_info.clevis_info().is_some() {\n            operation_loop(\n                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),\n                |blockdev| blockdev.unbind_clevis(),\n            )?;\n            Ok(true)\n        } else {\n            Ok(false)\n        }\n    }\n\n    /// Bind all devices in the given blockdev manager to a passphrase using the\n    /// given key description.\n    ///\n    /// * Returns Ok(true) if the binding was performed.\n    /// * Returns Ok(false) if the binding had already been previously performed and\n    /// nothing was changed.\n    /// * Returns Err(_) if an inconsistency was found in the metadata across pools\n    /// or binding failed.\n    pub fn bind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult<bool> {\n        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {\n            Some(ei) => ei,\n            None => {\n                return Err(StratisError::Msg(\n                    "Requested pool does not appear to be encrypted".to_string(),\n                ));\n            }\n        };\n\n        if let Some(kd) = encryption_info.key_description() {\n            if kd == key_desc {\n                if self.can_unlock(true, false) {\n                    Ok(false)\n                } else {\n                    Err(StratisError::Msg(format!(\n                        "Key description {} is registered in the metadata but the \\\n                            associated passphrase can\'t unlock the device; the \\\n                            associated passphrase may have changed since binding",\n                        key_desc.as_application_str(),\n                    )))\n                }\n            } else {\n                Err(StratisError::Msg(format!(\n                    "Block devices have already been bound with key description {}; \\\n                        requested key description {} can\'t be applied",\n                    key_desc.as_application_str(),\n                    kd.as_application_str(),\n                )))\n            }\n        } else {\n            operation_loop(\n                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),\n                |blockdev| blockdev.bind_keyring(key_desc),\n            )?;\n            Ok(true)\n        }\n    }\n\n    /// Unbind all devices in the given blockdev manager from the passphrase\n    /// associated with the key description.\n    ///\n    /// * Returns Ok(true) if the unbinding was performed.\n    /// * Returns Ok(false) if the unbinding had already been previously performed and\n    /// nothing was changed.\n    /// * Returns Err(_) if an inconsistency was found in the metadata across pools\n    /// or unbinding failed.\n    pub fn unbind_keyring(&mut self) -> StratisResult<bool> {\n        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {\n            Some(ei) => ei,\n            None => {\n                return Err(StratisError::Msg(\n                    "Requested pool does not appear to be encrypted".to_string(),\n                ));\n            }\n        };\n\n        if encryption_info.key_description().is_some() {\n            operation_loop(\n                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),\n                |blockdev| blockdev.unbind_keyring(),\n            )?;\n            Ok(true)\n        } else {\n            // is encrypted and key description is None\n            Ok(false)\n        }\n    }\n\n    /// Change the keyring passphrase associated with the block devices in\n    /// this pool.\n    ///\n    /// Returns:\n    /// * Ok(None) if the pool is not currently bound to a keyring passphrase.\n    /// * Ok(Some(true)) if the pool was successfully bound to the new key description.\n    /// * Ok(Some(false)) if the pool is already bound to this key description.\n    /// * Err(_) if an operation fails while changing the passphrase.\n    pub fn rebind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult<Option<bool>> {\n        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {\n            Some(ei) => ei,\n            None => {\n                return Err(StratisError::Msg(\n                    "Requested pool does not appear to be encrypted".to_string(),\n                ));\n            }\n        };\n\n        if encryption_info.key_description() == Some(key_desc) {\n            Ok(Some(false))\n        } else if encryption_info.key_description().is_some() {\n            // Keys are not the same but key description is present\n            operation_loop(\n                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),\n                |blockdev| blockdev.rebind_keyring(key_desc),\n            )?;\n            Ok(Some(true))\n        } else {\n            Ok(None)\n        }\n    }\n\n    /// Regenerate the Clevis bindings with the block devices in this pool using\n    /// the same configuration.\n    ///\n    /// The method for this rollback caches the initial Clevis metadata and\n    /// reverts all of the devices if there is a failure.\n    ///\n    /// This method returns StratisResult<()> because the Clevis regen command\n    /// will always change the metadata when successful. The command is not idempotent\n    /// so this method will either fail to regenerate the bindings or it will\n    /// result in a metadata change.\n    pub fn rebind_clevis(&mut self) -> StratisResult<()> {\n        let encryption_info = match pool_enc_to_enc!(self.encryption_info()) {\n            Some(ei) => ei,\n            None => {\n                return Err(StratisError::Msg(\n                    "Requested pool does not appear to be encrypted".to_string(),\n                ));\n            }\n        };\n\n        if encryption_info.clevis_info().is_none() {\n            Err(StratisError::Msg(\n                "Requested pool is not already bound to Clevis".to_string(),\n            ))\n        } else {\n            operation_loop(\n                self.blockdevs_mut().into_iter().map(|(_, bd)| bd),\n                |blockdev| blockdev.rebind_clevis(),\n            )?;\n\n            Ok(())\n        }\n    }\n\n    pub fn grow(&mut self, dev: DevUuid) -> StratisResult<bool> {\n        let bd = self\n            .block_devs\n            .iter_mut()\n            .find(|bd| bd.uuid() == dev)\n            .ok_or_else(|| {\n                StratisError::Msg(format!("Block device with UUID {} not found", dev))\n            })?;\n        bd.grow()\n    }\n\n    /// Tear down devicemapper devices for the block devices in this BlockDevMgr.\n    pub fn teardown(&mut self) -> StratisResult<()> {\n        let mut errors = Vec::new();\n        for bd in self.block_devs.iter_mut() {\n            if let Err(e) = bd.teardown() {\n                errors.push(e);\n            }\n        }\n\n        if errors.is_empty() {\n            Ok(())\n        } else {\n            Err(StratisError::BestEffortError("Failed to remove devicemapper devices for some or all physical devices in the pool".to_string(), errors))\n        }\n    }\n}\n\nfn operation_loop<\'a, I, A>(blockdevs: I, action: A) -> StratisResult<()>\nwhere\n    I: IntoIterator<Item = &\'a mut StratBlockDev>,\n    A: Fn(&mut StratBlockDev) -> StratisResult<()>,\n{\n    fn rollback_loop(\n        rollback_record: Vec<&mut StratBlockDev>,\n        headers: Vec<PathBuf>,\n        causal_error: StratisError,\n    ) -> StratisError {\n        // NOTE: Zip can be used here because the header will always be backed up before\n        // the operation is performed. As a result, the header iterator will always be\n        // equal to or longer than the blockdev record iterator which means all blockdevs\n        // that have had operations performed on them will always be restored.\n        for (blockdev, header) in rollback_record.into_iter().zip(headers) {\n            if let Err(e) = restore_luks_header(blockdev.devnode(), header.as_path()) {\n                warn!(\n                    "Failed to roll back device operation for device {}: {}",\n                    blockdev.physical_path().display(),\n                    e,\n                );\n                return StratisError::RollbackError {\n                    causal_error: Box::new(causal_error),\n                    rollback_error: Box::new(e),\n                    level: ActionAvailability::NoRequests,\n                };\n            }\n        }\n\n        causal_error\n    }\n\n    fn perform_operation<\'a, I, A>(tmp_dir: &TempDir, blockdevs: I, action: A) -> StratisResult<()>\n    where\n        I: IntoIterator<Item = &\'a mut StratBlockDev>,\n        A: Fn(&mut StratBlockDev) -> StratisResult<()>,\n    {\n        let mut original_headers = Vec::new();\n        let mut rollback_record = Vec::new();\n        for blockdev in blockdevs {\n            match back_up_luks_header(blockdev.physical_path(), tmp_dir) {\n                Ok(h) => original_headers.push(h),\n                Err(e) => return Err(rollback_loop(rollback_record, original_headers, e)),\n            };\n            let res = action(blockdev);\n            rollback_record.push(blockdev);\n            if let Err(error) = res {\n                return Err(rollback_loop(rollback_record, original_headers, error));\n            }\n        }\n\n        Ok(())\n    }\n\n    let tmp_dir = TempDir::new()?;\n    let res = perform_operation(&tmp_dir, blockdevs, action);\n    if let Err(e) = fs::remove_dir_all(tmp_dir.path()) {\n        warn!(\n            "Leaked temporary files at path {}: {}",\n            tmp_dir.path().display(),\n            e\n        );\n    }\n    res\n}\n\nimpl Recordable<Vec<BaseBlockDevSave>> for BlockDevMgr {\n    fn record(&self) -> Vec<BaseBlockDevSave> {\n        self.block_devs.iter().map(|bd| bd.record()).collect()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::{env, error::Error, path::Path};\n\n    use crate::engine::strat_engine::{\n        backstore::devices::{ProcessedPathInfos, UnownedDevices},\n        cmd,\n        names::KeyDescription,\n        ns::{unshare_namespace, MemoryFilesystem},\n        tests::{crypt, loopbacked, real},\n    };\n\n    use super::*;\n\n    fn get_devices(paths: &[&Path]) -> StratisResult<UnownedDevices> {\n        ProcessedPathInfos::try_from(paths)\n            .map(|ps| ps.unpack())\n            .and_then(|(sds, uds)| sds.error_on_not_empty().map(|_| uds))\n    }\n\n    /// Verify that initially,\n    /// size() - metadata_size() = avail_space().\n    /// After 2 Sectors have been allocated, that amount must also be included\n    /// in balance.\n    fn test_blockdevmgr_used(paths: &[&Path]) {\n        let pool_uuid = PoolUuid::new_v4();\n        let devices = get_devices(paths).unwrap();\n        let mut mgr =\n            BlockDevMgr::initialize(pool_uuid, devices, MDADataSize::default(), None).unwrap();\n        assert_eq!(mgr.avail_space() + mgr.metadata_size(), mgr.size());\n\n        let allocated = Sectors(2);\n        let transaction = mgr.request_space(&[allocated]).unwrap().unwrap();\n        mgr.commit_space(transaction).unwrap();\n        assert_eq!(\n            mgr.avail_space() + allocated + mgr.metadata_size(),\n            mgr.size()\n        );\n    }\n\n    #[test]\n    fn loop_test_blockdevmgr_used() {\n        loopbacked::test_with_spec(\n            &loopbacked::DeviceLimits::Range(1, 3, None),\n            test_blockdevmgr_used,\n        );\n    }\n\n    #[test]\n    fn real_test_blockdevmgr_used() {\n        real::test_with_spec(\n            &real::DeviceLimits::AtLeast(1, None, None),\n            test_blockdevmgr_used,\n        );\n    }\n\n    /// Test that the `BlockDevMgr` will add devices if the same key\n    /// is used to encrypted the existing devices and the added devices.\n    fn test_blockdevmgr_same_key(paths: &[&Path]) {\n        fn test_with_key(paths: &[&Path], key_desc: &KeyDescription) -> Result<(), Box<dyn Error>> {\n            let pool_uuid = PoolUuid::new_v4();\n\n            let devices1 = get_devices(&paths[..2])?;\n            let devices2 = get_devices(&paths[2..3])?;\n\n            let mut bdm = BlockDevMgr::initialize(\n                pool_uuid,\n                devices1,\n                MDADataSize::default(),\n                Some(&EncryptionInfo::KeyDesc(key_desc.clone())),\n            )?;\n\n            if bdm.add(pool_uuid, devices2).is_err() {\n                Err(Box::new(StratisError::Msg(\n                    "Adding a blockdev with the same key to an encrypted pool should succeed"\n                        .to_string(),\n                )))\n            } else {\n                Ok(())\n            }\n        }\n\n        crypt::insert_and_cleanup_key(paths, test_with_key);\n    }\n\n    #[test]\n    fn loop_test_blockdevmgr_same_key() {\n        loopbacked::test_with_spec(\n            &loopbacked::DeviceLimits::Exactly(3, None),\n            test_blockdevmgr_same_key,\n        );\n    }\n\n    #[test]\n    fn real_test_blockdevmgr_same_key() {\n        real::test_with_spec(\n            &real::DeviceLimits::Exactly(3, None, None),\n            test_blockdevmgr_same_key,\n        );\n    }\n\n    /// Test that the `BlockDevMgr` will not add devices if a different key\n    /// is present in the keyring than was used to encrypted the existing\n    /// devices.\n    fn test_blockdevmgr_changed_key(paths: &[&Path]) {\n        fn test_with_key(paths: &[&Path], key_desc: &KeyDescription) -> Result<(), Box<dyn Error>> {\n            let pool_uuid = PoolUuid::new_v4();\n\n            let devices1 = get_devices(&paths[..2])?;\n            let devices2 = get_devices(&paths[2..3])?;\n\n            let mut bdm = BlockDevMgr::initialize(\n                pool_uuid,\n                devices1,\n                MDADataSize::default(),\n                Some(&EncryptionInfo::KeyDesc(key_desc.clone())),\n            )?;\n\n            crypt::change_key(key_desc)?;\n\n            if bdm.add(pool_uuid, devices2).is_ok() {\n                Err(Box::new(StratisError::Msg(\n                    "Adding a blockdev with a new key to an encrypted pool should fail".to_string(),\n                )))\n            } else {\n                Ok(())\n            }\n        }\n\n        crypt::insert_and_cleanup_key(paths, test_with_key);\n    }\n\n    #[test]\n    fn loop_test_blockdevmgr_changed_key() {\n        loopbacked::test_with_spec(\n            &loopbacked::DeviceLimits::Exactly(3, None),\n            test_blockdevmgr_changed_key,\n        );\n    }\n\n    #[test]\n    fn real_test_blockdevmgr_changed_key() {\n        real::test_with_spec(\n            &real::DeviceLimits::Exactly(3, None, None),\n            test_blockdevmgr_changed_key,\n        );\n    }\n\n    /// Verify that it is impossible to steal blockdevs from another Stratis\n    /// pool.\n    /// 1. Initialize devices with pool uuid.\n    /// 2. Initializing again with different uuid must fail.\n    /// 3. Adding the devices must succeed, because they already belong.\n    fn test_initialization_add_stratis(paths: &[&Path]) {\n        assert!(paths.len() > 1);\n        let (paths1, paths2) = paths.split_at(paths.len() / 2);\n\n        let uuid = PoolUuid::new_v4();\n        let uuid2 = PoolUuid::new_v4();\n\n        let bd_mgr = BlockDevMgr::initialize(\n            uuid,\n            get_devices(paths1).unwrap(),\n            MDADataSize::default(),\n            None,\n        )\n        .unwrap();\n        cmd::udev_settle().unwrap();\n\n        assert_matches!(get_devices(paths1), Err(_));\n\n        assert!(ProcessedPathInfos::try_from(paths1)\n            .unwrap()\n            .unpack()\n            .0\n            .partition(uuid2)\n            .0\n            .is_empty());\n\n        assert!(!ProcessedPathInfos::try_from(paths1)\n            .unwrap()\n            .unpack()\n            .0\n            .partition(uuid)\n            .0\n            .is_empty());\n\n        BlockDevMgr::initialize(\n            uuid,\n            get_devices(paths2).unwrap(),\n            MDADataSize::default(),\n            None,\n        )\n        .unwrap();\n\n        cmd::udev_settle().unwrap();\n\n        assert!(!ProcessedPathInfos::try_from(paths2)\n            .unwrap()\n            .unpack()\n            .0\n            .partition(uuid)\n            .0\n            .is_empty());\n\n        bd_mgr.invariant()\n    }\n\n    #[test]\n    fn loop_test_initialization_stratis() {\n        loopbacked::test_with_spec(\n            &loopbacked::DeviceLimits::Range(2, 3, None),\n            test_initialization_add_stratis,\n        );\n    }\n\n    #[test]\n    fn real_test_initialization_stratis() {\n        real::test_with_spec(\n            &real::DeviceLimits::AtLeast(2, None, None),\n            test_initialization_add_stratis,\n        );\n    }\n\n    fn test_clevis_initialize(paths: &[&Path]) {\n        unshare_namespace().unwrap();\n        let _memfs = MemoryFilesystem::new().unwrap();\n        let pool_uuid = PoolUuid::new_v4();\n        let mut mgr = BlockDevMgr::initialize(\n            pool_uuid,\n            get_devices(paths).unwrap(),\n            MDADataSize::default(),\n            Some(&EncryptionInfo::ClevisInfo((\n                "tang".to_string(),\n                json!({"url": env::var("TANG_URL").unwrap(), "stratis:tang:trust_url": true}),\n            ))),\n        )\n        .unwrap();\n        cmd::udev_settle().unwrap();\n\n        matches!(\n            mgr.bind_clevis(\n                "tang",\n                &json!({"url": env::var("TANG_URL").unwrap(), "stratis:tang:trust_url": true})\n            ),\n            Ok(false)\n        );\n    }\n\n    #[test]\n    fn clevis_real_test_initialize() {\n        real::test_with_spec(\n            &real::DeviceLimits::AtLeast(2, None, None),\n            test_clevis_initialize,\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn clevis_real_should_fail_test_initialize() {\n        real::test_with_spec(\n            &real::DeviceLimits::AtLeast(2, None, None),\n            test_clevis_initialize,\n        );\n    }\n\n    #[test]\n    fn clevis_loop_test_initialize() {\n        loopbacked::test_with_spec(\n            &loopbacked::DeviceLimits::Range(2, 4, None),\n            test_clevis_initialize,\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn clevis_loop_should_fail_test_initialize() {\n        loopbacked::test_with_spec(\n            &loopbacked::DeviceLimits::Range(2, 4, None),\n            test_clevis_initialize,\n        );\n    }\n\n    fn test_clevis_both_initialize(paths: &[&Path]) {\n        fn test_both(paths: &[&Path], key_desc: &KeyDescription) -> Result<(), Box<dyn Error>> {\n            unshare_namespace()?;\n            let _memfs = MemoryFilesystem::new().unwrap();\n            let pool_uuid = PoolUuid::new_v4();\n            let mut mgr = BlockDevMgr::initialize(\n                pool_uuid,\n                get_devices(paths).unwrap(),\n                MDADataSize::default(),\n                Some(&EncryptionInfo::Both(\n                    key_desc.clone(),\n                    (\n                        "tang".to_string(),\n                        json!({"url": env::var("TANG_URL")?, "stratis:tang:trust_url": true}),\n                    ),\n                )),\n            )?;\n            cmd::udev_settle()?;\n\n            if mgr.bind_clevis(\n                "tang",\n                &json!({"url": env::var("TANG_URL")?, "stratis:tang:trust_url": true}),\n            )? {\n                return Err(Box::new(StratisError::Msg(\n                    "Clevis bind idempotence test failed".to_string(),\n                )));\n            }\n            if mgr.bind_keyring(key_desc)? {\n                return Err(Box::new(StratisError::Msg(\n                    "Keyring bind idempotence test failed".to_string(),\n                )));\n            }\n\n            if !(mgr.unbind_clevis()?) {\n                return Err(Box::new(StratisError::Msg(\n                    "Clevis unbind test failed".to_string(),\n                )));\n            }\n            if mgr.unbind_clevis()? {\n                return Err(Box::new(StratisError::Msg(\n                    "Clevis unbind idempotence test failed".to_string(),\n                )));\n            }\n            if mgr.unbind_keyring().is_ok() {\n                return Err(Box::new(StratisError::Msg(\n                    "Keyring unbind check test failed".to_string(),\n                )));\n            }\n\n            if !(mgr.bind_clevis(\n                "tang",\n                &json!({"url": env::var("TANG_URL")?, "stratis:tang:trust_url": true}),\n            )?) {\n                return Err(Box::new(StratisError::Msg(\n                    "Clevis bind test failed".to_string(),\n                )));\n            }\n            if !(mgr.unbind_keyring()?) {\n                return Err(Box::new(StratisError::Msg(\n                    "Keyring unbind test failed".to_string(),\n                )));\n            }\n            if mgr.unbind_keyring()? {\n                return Err(Box::new(StratisError::Msg(\n                    "Keyring unbind idempotence test failed".to_string(),\n                )));\n            }\n            if mgr.unbind_clevis().is_ok() {\n                return Err(Box::new(StratisError::Msg(\n                    "Clevis unbind check test failed".to_string(),\n                )));\n            }\n\n            if !(mgr.bind_keyring(key_desc)?) {\n                return Err(Box::new(StratisError::Msg(\n                    "Keyring bind test failed".to_string(),\n                )));\n            }\n\n            Ok(())\n        }\n\n        crypt::insert_and_cleanup_key(paths, test_both);\n    }\n\n    #[test]\n    fn clevis_real_test_both_initialize() {\n        real::test_with_spec(\n            &real::DeviceLimits::AtLeast(2, None, None),\n            test_clevis_both_initialize,\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn clevis_real_should_fail_test_both_initialize() {\n        real::test_with_spec(\n            &real::DeviceLimits::AtLeast(2, None, None),\n            test_clevis_both_initialize,\n        );\n    }\n\n    #[test]\n    fn clevis_loop_test_both_initialize() {\n        loopbacked::test_with_spec(\n            &loopbacked::DeviceLimits::Range(2, 4, None),\n            test_clevis_both_initialize,\n        );\n    }\n\n    #[test]\n    #[should_panic]\n    fn clevis_loop_should_fail_test_both_initialize() {\n        loopbacked::test_with_spec(\n            &loopbacked::DeviceLimits::Range(2, 4, None),\n            test_clevis_both_initialize,\n        );\n    }\n}\n', 'message': 'Merge pull request #793 from mulkieran/master-blockdevs-3\n\nHandle change in blockdev size on setup', 'author_name': 'GitHub', 'lang': 'rs'}