<commit_before>// 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/.

//! Discover or identify devices that may belong to Stratis.
//!
//! This module contains methods for finding all block devices that may belong
//! to Stratis, generally run when Stratis starts up, and other methods that
//! may be used to classify a single unknown block device.
//!
//! The methods rely to a greater or lesser extent on libudev.
//!
//! They have the following invocation heirarchy:
//! find_all*
//!  |
//! find_all_*_devices
//!  |
//! identify_*_device
//!  |
//! process_*_device
//!
//! The primary purpose of the find* methods is to construct a udev
//! enumeration and to properly process each of the udev database entries
//! found.
//!
//! The primary purpose of the identify_* methods is to use udev to identify
//! a single device and take the appropriate action based on that
//! identification.
//!
//! The primary purpose of the process_* methods is to gather up Stratis
//! device identifiers.
//!
//! Each method is expected to be invoked in a particular situation which
//! is guaranteed by the method that invokes it. The methods are not,
//! in general, general purpose methods that can be used in any situation.
//!
//! find_all is public because it is the method that is invoked by the
//! engine on startup. find_all_block_devices_with_stratis_signatures is
//! public for use in testing. identify_block_device is public because it
//! is suitable for identifying a block device associated with a uevent,
//! as the situation in which the uevent is handled is equivalent to that
//! provided by the execution of the
//! find_all_block_devices_with_stratis_signatures method.

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

use devicemapper::Device;

use crate::engine::{
    strat_engine::backstore::{
        metadata::{device_identifiers, StratisIdentifiers},
        udev::{block_enumerator, decide_ownership, UdevOwnership},
    },
    types::PoolUuid,
};

// A miscellaneous group of identifiers found when identifiying a Stratis
// device.
pub type StratisInfo = (StratisIdentifiers, Device, PathBuf);

// A wrapper for obtaining the device number as a devicemapper Device
// which interprets absence of the value as an error, which it is in this
// context.
fn device_to_devno_wrapper(device: &libudev::Device) -> Result<Device, String> {
    device
        .devnum()
        .ok_or_else(|| "udev entry did not contain a device number".into())
        .map(Device::from)
}

// A wrapper around the metadata module's device_identifers method
// which also handles failure to open a device for reading.
// Returns an error if the device could not be opened for reading.
// Returns Ok(Err(...)) if there was an error while reading the
// Stratis identifiers from the device.
// Returns Ok(Ok(None)) if the identifers did not appear to be on
// the device.
fn device_identifiers_wrapper(
    devnode: &Path,
) -> Result<Result<Option<StratisIdentifiers>, String>, String> {
    OpenOptions::new()
        .read(true)
        .open(devnode)
        .as_mut()
        .map_err(|err| {
            format!(
                "device {} could not be opened for reading: {}",
                devnode.display(),
                err
            )
        })
        .map(|f| {
            device_identifiers(f).map_err(|err| {
                format!(
                    "encountered an error while reading Stratis header for device {}: {}",
                    devnode.display(),
                    err
                )
            })
        })
}

/// Process a device which udev information indicates is a Stratis device.
fn process_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) | (_, Ok(Err(err))) => {
                    warn!("udev identified device {} as a Stratis device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                (_, Ok(Ok(None))) => {
                    warn!("udev identified device {} as a Stratis device but there appeared to be no Stratis metadata on the device, disregarding the device",
                          devnode.display());
                    None
                }
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a Stratis device, but the udev entry for the device had no device node, disregarding device");
            None
        }
    }
}

/// Process a device which udev information indicates is unowned.
fn process_unowned_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) => {
                    warn!("udev identified device {} as a block device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                // FIXME: Refine error return in StaticHeader::setup(),
                // so it can be used to distinguish between signficant
                // and insignficant errors and then use that ability to
                // distinguish here between different levels of
                // severity.
                (_, Ok(Err(err))) => {
                    debug!("udev identified device {} as a block device but {}, disregarding the device",
                           devnode.display(),
                           err);
                    None
                }
                (_, Ok(Ok(None))) => None,
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a block device, but the udev entry for the device had no device node, disregarding the device");
            None
        }
    }
}

// Use udev to identify all block devices and return the subset of those
// that have Stratis signatures.
pub fn find_all_block_devices_with_stratis_signatures(
) -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_block_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });

    Ok(pool_map)
}

// Find all devices identified by udev as Stratis devices.
fn find_all_stratis_devices() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;
    enumerator.match_property("ID_FS_TYPE", "stratis")?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_stratis_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });
    Ok(pool_map)
}

// Identify a device that udev enumeration has already picked up as a Stratis
// device. Return None if the device does not, after all, appear to be a Stratis
// device. Log anything unusual at an appropriate level.
fn identify_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        warn!("Found a udev entry for a device identified as a Stratis device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!("Could not determine ownership of a block device identified as a Stratis device by udev, disregarding the device: {}",
                  err);
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::MultipathMember => None,
            _ => {
                warn!("udev enumeration identified this device as a Stratis block device but on further examination udev identifies it as a {}",
                      ownership);
                None
            }
        },
    }
}

/// Identify a block device in the context where a udev event has been
/// captured for some block device. Return None if the device does not
/// appear to be a Stratis device. Log at an appropriate level on all errors.
pub fn identify_block_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        debug!("Found a udev entry for a device identified as a block device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!(
                "Could not determine ownership of a udev block device, disregarding the device: {}",
                err
            );
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::Unowned => process_unowned_device(dev),
            _ => None,
        },
    }
}

/// Retrieve all block devices that should be made use of by the
/// Stratis engine. This excludes Stratis block devices that appear to be
/// multipath members.
///
/// Includes a fallback path, which is used if no Stratis block devices are
/// found using the obvious udev property- and enumerator-based approach.
/// This fallback path is more expensive, because it must search all block
/// devices via udev rather than just all Stratis block devices.
///
/// Omits any device that appears problematic in some way.
///
/// Return an error only on a failure to construct or scan with a udev
/// enumerator.
///
/// Returns a map of pool uuids to a map of devices to devnodes for each pool.
pub fn find_all() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    info!("Beginning initial search for Stratis block devices");
    let pool_map = find_all_stratis_devices()?;

    if pool_map.is_empty() {
        // No Stratis devices have been found, possible reasons are:
        // 1. There are none
        // 2. There are many incomplete udev entries, because this code is
        // being run before the udev database is populated.
        //
        // Try again to find Stratis block devices, but this time enumerate
        // all block devices, not just all the ones that can be identified
        // as Stratis blockdevs by udev, and process only those that appear
        // unclaimed or appear to be claimed by Stratis (and not
        // multipath members).

        info!("Could not identify any Stratis devices by a udev search for devices with ID_FS_TYPE=\"stratis\"; using fallback search mechanism");

        find_all_block_devices_with_stratis_signatures()
    } else {
        Ok(pool_map)
    }
}

#[cfg(test)]
mod tests {

    use uuid::Uuid;

    use crate::engine::strat_engine::{
        backstore::{
            devices::{initialize_devices, process_devices},
            metadata::MDADataSize,
            udev::block_device_apply,
        },
        cmd::create_fs,
        tests::{loopbacked, real},
    };

    use super::*;

    /// Test that the process_*_device methods return the expected
    /// pool UUID and device node for initialized paths.
    fn test_process_device_initialized(paths: &[&Path]) {
        assert!(!paths.is_empty());

        let pool_uuid = Uuid::new_v4();

        initialize_devices(
            process_devices(paths)
                .unwrap()
                .into_iter()
                .map(|(info, _)| info)
                .collect(),
            pool_uuid,
            MDADataSize::default(),
        )
        .unwrap();

        for path in paths {
            let (identifiers, _, dev_node) =
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap()
                    .unwrap();
            assert_eq!(identifiers.pool_uuid, pool_uuid);
            assert_eq!(&&dev_node, path);

            let (identifiers, _, dev_node) =
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap()
                    .unwrap();
            assert_eq!(identifiers.pool_uuid, pool_uuid);
            assert_eq!(&&dev_node, path);
        }
    }

    #[test]
    fn loop_test_process_device_initialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_initialized,
        );
    }

    #[test]
    fn real_test_process_device_initialized() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(1, None, None),
            test_process_device_initialized,
        );
    }

    #[test]
    fn travis_test_process_device_initialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_initialized,
        );
    }

    /// Test that the process_*_device methods return None if the device is
    /// not a Stratis device. Strictly speaking, the methods are only supposed
    /// to be called in particular contexts, the situation where the device
    /// is claimed by a filesystem should be excluded by udev, which should
    /// identify the device as Theirs. But the methods should return the
    /// correct result in this situation, regardless, although their log
    /// messages will not precisely match their actual situation, but rather
    /// their expected context.
    fn test_process_device_uninitialized(paths: &[&Path]) {
        assert!(!paths.is_empty());

        for path in paths {
            assert_eq!(
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
            assert_eq!(
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
        }

        for path in paths {
            create_fs(path, None).unwrap();
            assert_eq!(
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
            assert_eq!(
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
        }
    }

    #[test]
    fn loop_test_process_device_uninitialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_uninitialized,
        );
    }

    #[test]
    fn real_test_process_device_uninitialized() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(1, None, None),
            test_process_device_uninitialized,
        );
    }

    #[test]
    fn travis_test_process_device_uninitialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_uninitialized,
        );
    }
}
<commit_msg>No longer use fallback path for finding Stratis devices<commit_after>// 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/.

//! Discover or identify devices that may belong to Stratis.
//!
//! This module contains methods for finding all block devices that may belong
//! to Stratis, generally run when Stratis starts up, and other methods that
//! may be used to classify a single unknown block device.
//!
//! The methods rely to a greater or lesser extent on libudev.
//!
//! They have the following invocation heirarchy:
//! find_all*
//!  |
//! find_all_*_devices
//!  |
//! identify_*_device
//!  |
//! process_*_device
//!
//! The primary purpose of the find* methods is to construct a udev
//! enumeration and to properly process each of the udev database entries
//! found.
//!
//! The primary purpose of the identify_* methods is to use udev to identify
//! a single device and take the appropriate action based on that
//! identification.
//!
//! The primary purpose of the process_* methods is to gather up Stratis
//! device identifiers.
//!
//! Each method is expected to be invoked in a particular situation which
//! is guaranteed by the method that invokes it. The methods are not,
//! in general, general purpose methods that can be used in any situation.
//!
//! find_all is public because it is the method that is invoked by the
//! engine on startup. find_all_block_devices_with_stratis_signatures is
//! public for use in testing. identify_block_device is public because it
//! is suitable for identifying a block device associated with a uevent,
//! as the situation in which the uevent is handled is equivalent to that
//! provided by the execution of the
//! find_all_block_devices_with_stratis_signatures method.

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

use devicemapper::Device;

use crate::engine::{
    strat_engine::backstore::{
        metadata::{device_identifiers, StratisIdentifiers},
        udev::{block_enumerator, decide_ownership, UdevOwnership},
    },
    types::PoolUuid,
};

// A miscellaneous group of identifiers found when identifiying a Stratis
// device.
pub type StratisInfo = (StratisIdentifiers, Device, PathBuf);

// A wrapper for obtaining the device number as a devicemapper Device
// which interprets absence of the value as an error, which it is in this
// context.
fn device_to_devno_wrapper(device: &libudev::Device) -> Result<Device, String> {
    device
        .devnum()
        .ok_or_else(|| "udev entry did not contain a device number".into())
        .map(Device::from)
}

// A wrapper around the metadata module's device_identifers method
// which also handles failure to open a device for reading.
// Returns an error if the device could not be opened for reading.
// Returns Ok(Err(...)) if there was an error while reading the
// Stratis identifiers from the device.
// Returns Ok(Ok(None)) if the identifers did not appear to be on
// the device.
fn device_identifiers_wrapper(
    devnode: &Path,
) -> Result<Result<Option<StratisIdentifiers>, String>, String> {
    OpenOptions::new()
        .read(true)
        .open(devnode)
        .as_mut()
        .map_err(|err| {
            format!(
                "device {} could not be opened for reading: {}",
                devnode.display(),
                err
            )
        })
        .map(|f| {
            device_identifiers(f).map_err(|err| {
                format!(
                    "encountered an error while reading Stratis header for device {}: {}",
                    devnode.display(),
                    err
                )
            })
        })
}

/// Process a device which udev information indicates is a Stratis device.
fn process_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) | (_, Ok(Err(err))) => {
                    warn!("udev identified device {} as a Stratis device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                (_, Ok(Ok(None))) => {
                    warn!("udev identified device {} as a Stratis device but there appeared to be no Stratis metadata on the device, disregarding the device",
                          devnode.display());
                    None
                }
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a Stratis device, but the udev entry for the device had no device node, disregarding device");
            None
        }
    }
}

/// Process a device which udev information indicates is unowned.
fn process_unowned_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) => {
                    warn!("udev identified device {} as a block device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                // FIXME: Refine error return in StaticHeader::setup(),
                // so it can be used to distinguish between signficant
                // and insignficant errors and then use that ability to
                // distinguish here between different levels of
                // severity.
                (_, Ok(Err(err))) => {
                    debug!("udev identified device {} as a block device but {}, disregarding the device",
                           devnode.display(),
                           err);
                    None
                }
                (_, Ok(Ok(None))) => None,
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a block device, but the udev entry for the device had no device node, disregarding the device");
            None
        }
    }
}

// Use udev to identify all block devices and return the subset of those
// that have Stratis signatures.
#[cfg(test)]
pub fn find_all_block_devices_with_stratis_signatures(
) -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_block_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });

    Ok(pool_map)
}

// Find all devices identified by udev as Stratis devices.
fn find_all_stratis_devices() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;
    enumerator.match_property("ID_FS_TYPE", "stratis")?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_stratis_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });
    Ok(pool_map)
}

// Identify a device that udev enumeration has already picked up as a Stratis
// device. Return None if the device does not, after all, appear to be a Stratis
// device. Log anything unusual at an appropriate level.
fn identify_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        warn!("Found a udev entry for a device identified as a Stratis device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!("Could not determine ownership of a block device identified as a Stratis device by udev, disregarding the device: {}",
                  err);
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::MultipathMember => None,
            _ => {
                warn!("udev enumeration identified this device as a Stratis block device but on further examination udev identifies it as a {}",
                      ownership);
                None
            }
        },
    }
}

/// Identify a block device in the context where a udev event has been
/// captured for some block device. Return None if the device does not
/// appear to be a Stratis device. Log at an appropriate level on all errors.
pub fn identify_block_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        debug!("Found a udev entry for a device identified as a block device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!(
                "Could not determine ownership of a udev block device, disregarding the device: {}",
                err
            );
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::Unowned => process_unowned_device(dev),
            _ => None,
        },
    }
}

/// Retrieve all block devices that should be made use of by the
/// Stratis engine. This excludes Stratis block devices that appear to be
/// multipath members.
///
/// Includes a fallback path, which is used if no Stratis block devices are
/// found using the obvious udev property- and enumerator-based approach.
/// This fallback path is more expensive, because it must search all block
/// devices via udev rather than just all Stratis block devices.
///
/// Omits any device that appears problematic in some way.
///
/// Return an error only on a failure to construct or scan with a udev
/// enumerator.
///
/// Returns a map of pool uuids to a map of devices to devnodes for each pool.
pub fn find_all() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    info!("Beginning initial search for Stratis block devices");
    find_all_stratis_devices()
}

#[cfg(test)]
mod tests {

    use uuid::Uuid;

    use crate::engine::strat_engine::{
        backstore::{
            devices::{initialize_devices, process_devices},
            metadata::MDADataSize,
            udev::block_device_apply,
        },
        cmd::create_fs,
        tests::{loopbacked, real},
    };

    use super::*;

    /// Test that the process_*_device methods return the expected
    /// pool UUID and device node for initialized paths.
    fn test_process_device_initialized(paths: &[&Path]) {
        assert!(!paths.is_empty());

        let pool_uuid = Uuid::new_v4();

        initialize_devices(
            process_devices(paths)
                .unwrap()
                .into_iter()
                .map(|(info, _)| info)
                .collect(),
            pool_uuid,
            MDADataSize::default(),
        )
        .unwrap();

        for path in paths {
            let (identifiers, _, dev_node) =
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap()
                    .unwrap();
            assert_eq!(identifiers.pool_uuid, pool_uuid);
            assert_eq!(&&dev_node, path);

            let (identifiers, _, dev_node) =
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap()
                    .unwrap();
            assert_eq!(identifiers.pool_uuid, pool_uuid);
            assert_eq!(&&dev_node, path);
        }
    }

    #[test]
    fn loop_test_process_device_initialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_initialized,
        );
    }

    #[test]
    fn real_test_process_device_initialized() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(1, None, None),
            test_process_device_initialized,
        );
    }

    #[test]
    fn travis_test_process_device_initialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_initialized,
        );
    }

    /// Test that the process_*_device methods return None if the device is
    /// not a Stratis device. Strictly speaking, the methods are only supposed
    /// to be called in particular contexts, the situation where the device
    /// is claimed by a filesystem should be excluded by udev, which should
    /// identify the device as Theirs. But the methods should return the
    /// correct result in this situation, regardless, although their log
    /// messages will not precisely match their actual situation, but rather
    /// their expected context.
    fn test_process_device_uninitialized(paths: &[&Path]) {
        assert!(!paths.is_empty());

        for path in paths {
            assert_eq!(
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
            assert_eq!(
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
        }

        for path in paths {
            create_fs(path, None).unwrap();
            assert_eq!(
                block_device_apply(path, |dev| process_stratis_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
            assert_eq!(
                block_device_apply(path, |dev| process_unowned_device(dev))
                    .unwrap()
                    .unwrap(),
                None
            );
        }
    }

    #[test]
    fn loop_test_process_device_uninitialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_uninitialized,
        );
    }

    #[test]
    fn real_test_process_device_uninitialized() {
        real::test_with_spec(
            &real::DeviceLimits::Exactly(1, None, None),
            test_process_device_uninitialized,
        );
    }

    #[test]
    fn travis_test_process_device_uninitialized() {
        loopbacked::test_with_spec(
            &loopbacked::DeviceLimits::Exactly(1, None),
            test_process_device_uninitialized,
        );
    }
}
<|endoftext|>


<commit_before>// 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/.

// Discover or identify devices that may belong to Stratis.

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

use libudev;

use devicemapper::Device;

use crate::engine::{
    strat_engine::backstore::{
        metadata::{device_identifiers, StratisIdentifiers},
        udev::{block_enumerator, decide_ownership, UdevOwnership},
    },
    types::PoolUuid,
};

// A wrapper for obtaining the device number as a devicemapper Device
// which interprets absence of the value as an error, which it is in this
// context.
fn device_to_devno_wrapper(device: &libudev::Device) -> Result<Device, String> {
    device
        .devnum()
        .ok_or_else(|| "udev entry did not contain a device number".into())
        .map(Device::from)
}

// A wrapper around the metadata module's device_identifers method
// which also handles failure to open a device for reading.
// Returns an error if the device could not be opened for reading.
// Returns Ok(Err(...)) if there was an error while reading the
// Stratis identifiers from the device.
// Returns Ok(Ok(None)) if the identifers did not appear to be on
// the device.
fn device_identifiers_wrapper(
    devnode: &Path,
) -> Result<Result<Option<StratisIdentifiers>, String>, String> {
    OpenOptions::new()
        .read(true)
        .open(devnode)
        .as_mut()
        .map_err(|err| {
            format!(
                "device {} could not be opened for reading: {}",
                devnode.display(),
                err
            )
        })
        .map(|f| {
            device_identifiers(f).map_err(|err| {
                format!(
                    "encountered an error while reading Stratis header for device {}: {}",
                    devnode.display(),
                    err
                )
            })
        })
}

/// Process a device which udev information indicates is a Stratis device.
fn process_stratis_device(dev: &libudev::Device) -> Option<(StratisIdentifiers, Device, PathBuf)> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) | (_, Ok(Err(err))) => {
                    warn!("udev identified device {} as a Stratis device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                (_, Ok(Ok(None))) => {
                    warn!("udev identified device {} as a Stratis device but there appeared to be no Stratis metadata on the device, disregarding the device",
                          devnode.display());
                    None
                }
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a Stratis device, but the udev entry for the device had no device node, disregarding device");
            None
        }
    }
}

/// Process a device which udev information indicates is unowned.
fn process_unowned_device(dev: &libudev::Device) -> Option<(StratisIdentifiers, Device, PathBuf)> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) => {
                    warn!("udev identified device {} as a block device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                // FIXME: Refine error return in StaticHeader::setup(),
                // so it can be used to distinguish between signficant
                // and insignficant errors and then use that ability to
                // distinguish here between different levels of
                // severity.
                (_, Ok(Err(err))) => {
                    debug!("udev identified device {} as a block device but {}, disregarding the device",
                           devnode.display(),
                           err);
                    None
                }
                (_, Ok(Ok(None))) => None,
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a block device, but the udev entry for the device had no device node, disregarding the device");
            None
        }
    }
}

// Use udev to identify all block devices and return the subset of those
// that have Stratis signatures.
pub fn find_all_block_devices_with_stratis_signatures(
) -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_block_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });

    Ok(pool_map)
}

// Find all devices identified by udev as Stratis devices.
fn find_all_stratis_devices() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;
    enumerator.match_property("ID_FS_TYPE", "stratis")?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_stratis_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });
    Ok(pool_map)
}

// Identify a device that udev enumeration has already picked up as a Stratis
// device. Return None if the device does not, after all, appear to be a Stratis
// device. Log anything unusual at an appropriate level.
fn identify_stratis_device(dev: &libudev::Device) -> Option<(StratisIdentifiers, Device, PathBuf)> {
    let initialized = dev.is_initialized();
    if !initialized {
        warn!("Found a udev entry for a device identified as a Stratis device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!("Could not determine ownership of a block device identified as a Stratis device by udev, disregarding the device: {}",
                  err);
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::MultipathMember => None,
            _ => {
                warn!("udev enumeration identified this device as a Stratis block device but on further examination udev identifies it as a {}",
                      ownership);
                None
            }
        },
    }
}

/// Identify a block device in the context where a udev event has been
/// captured for some block device. Return None if the device does not
/// appear to be a Stratis device. Log at an appropriate level on all errors.
pub fn identify_block_device(
    dev: &libudev::Device,
) -> Option<(StratisIdentifiers, Device, PathBuf)> {
    let initialized = dev.is_initialized();
    if !initialized {
        debug!("Found a udev entry for a device identified as a block device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!(
                "Could not determine ownership of a udev block device, disregarding the device: {}",
                err
            );
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::Unowned => process_unowned_device(dev),
            _ => None,
        },
    }
}

/// Retrieve all block devices that should be made use of by the
/// Stratis engine. This excludes Stratis block devices that appear to be
/// multipath members.
///
/// Includes a fallback path, which is used if no Stratis block devices are
/// found using the obvious udev property- and enumerator-based approach.
/// This fallback path is more expensive, because it must search all block
/// devices via udev rather than just all Stratis block devices.
///
/// Omits any device that appears problematic in some way.
///
/// Return an error only on a failure to construct or scan with a udev
/// enumerator.
///
/// Returns a map of pool uuids to a map of devices to devnodes for each pool.
pub fn find_all() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    info!("Beginning initial search for Stratis block devices");
    let pool_map = find_all_stratis_devices()?;

    if pool_map.is_empty() {
        // No Stratis devices have been found, possible reasons are:
        // 1. There are none
        // 2. There are many incomplete udev entries, because this code is
        // being run before the udev database is populated.
        //
        // Try again to find Stratis block devices, but this time enumerate
        // all block devices, not just all the ones that can be identified
        // as Stratis blockdevs by udev, and process only those that appear
        // unclaimed or appear to be claimed by Stratis (and not
        // multipath members).

        info!("Could not identify any Stratis devices by a udev search for devices with ID_FS_TYPE=\"stratis\"; using fallback search mechanism");

        find_all_block_devices_with_stratis_signatures()
    } else {
        Ok(pool_map)
    }
}
<commit_msg>Make a type alias for a return type<commit_after>// 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/.

// Discover or identify devices that may belong to Stratis.

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

use libudev;

use devicemapper::Device;

use crate::engine::{
    strat_engine::backstore::{
        metadata::{device_identifiers, StratisIdentifiers},
        udev::{block_enumerator, decide_ownership, UdevOwnership},
    },
    types::PoolUuid,
};

// A miscellaneous group of identifiers found when identifiying a Stratis
// device.
pub type StratisInfo = (StratisIdentifiers, Device, PathBuf);

// A wrapper for obtaining the device number as a devicemapper Device
// which interprets absence of the value as an error, which it is in this
// context.
fn device_to_devno_wrapper(device: &libudev::Device) -> Result<Device, String> {
    device
        .devnum()
        .ok_or_else(|| "udev entry did not contain a device number".into())
        .map(Device::from)
}

// A wrapper around the metadata module's device_identifers method
// which also handles failure to open a device for reading.
// Returns an error if the device could not be opened for reading.
// Returns Ok(Err(...)) if there was an error while reading the
// Stratis identifiers from the device.
// Returns Ok(Ok(None)) if the identifers did not appear to be on
// the device.
fn device_identifiers_wrapper(
    devnode: &Path,
) -> Result<Result<Option<StratisIdentifiers>, String>, String> {
    OpenOptions::new()
        .read(true)
        .open(devnode)
        .as_mut()
        .map_err(|err| {
            format!(
                "device {} could not be opened for reading: {}",
                devnode.display(),
                err
            )
        })
        .map(|f| {
            device_identifiers(f).map_err(|err| {
                format!(
                    "encountered an error while reading Stratis header for device {}: {}",
                    devnode.display(),
                    err
                )
            })
        })
}

/// Process a device which udev information indicates is a Stratis device.
fn process_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) | (_, Ok(Err(err))) => {
                    warn!("udev identified device {} as a Stratis device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                (_, Ok(Ok(None))) => {
                    warn!("udev identified device {} as a Stratis device but there appeared to be no Stratis metadata on the device, disregarding the device",
                          devnode.display());
                    None
                }
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a Stratis device, but the udev entry for the device had no device node, disregarding device");
            None
        }
    }
}

/// Process a device which udev information indicates is unowned.
fn process_unowned_device(dev: &libudev::Device) -> Option<StratisInfo> {
    match dev.devnode() {
        Some(devnode) => {
            match (
                device_to_devno_wrapper(dev),
                device_identifiers_wrapper(devnode),
            ) {
                (Err(err), _) | (_, Err(err)) => {
                    warn!("udev identified device {} as a block device but {}, disregarding the device",
                          devnode.display(),
                          err);
                    None
                }
                // FIXME: Refine error return in StaticHeader::setup(),
                // so it can be used to distinguish between signficant
                // and insignficant errors and then use that ability to
                // distinguish here between different levels of
                // severity.
                (_, Ok(Err(err))) => {
                    debug!("udev identified device {} as a block device but {}, disregarding the device",
                           devnode.display(),
                           err);
                    None
                }
                (_, Ok(Ok(None))) => None,
                (Ok(devno), Ok(Ok(Some(identifiers)))) => {
                    Some((identifiers, devno, devnode.to_path_buf()))
                }
            }
        }
        None => {
            warn!("udev identified a device as a block device, but the udev entry for the device had no device node, disregarding the device");
            None
        }
    }
}

// Use udev to identify all block devices and return the subset of those
// that have Stratis signatures.
pub fn find_all_block_devices_with_stratis_signatures(
) -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_block_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });

    Ok(pool_map)
}

// Find all devices identified by udev as Stratis devices.
fn find_all_stratis_devices() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;
    enumerator.match_property("ID_FS_TYPE", "stratis")?;

    let pool_map = enumerator
        .scan_devices()?
        .filter_map(|dev| identify_stratis_device(&dev))
        .fold(HashMap::new(), |mut acc, (identifiers, device, devnode)| {
            acc.entry(identifiers.pool_uuid)
                .or_insert_with(HashMap::new)
                .insert(device, devnode);
            acc
        });
    Ok(pool_map)
}

// Identify a device that udev enumeration has already picked up as a Stratis
// device. Return None if the device does not, after all, appear to be a Stratis
// device. Log anything unusual at an appropriate level.
fn identify_stratis_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        warn!("Found a udev entry for a device identified as a Stratis device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!("Could not determine ownership of a block device identified as a Stratis device by udev, disregarding the device: {}",
                  err);
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::MultipathMember => None,
            _ => {
                warn!("udev enumeration identified this device as a Stratis block device but on further examination udev identifies it as a {}",
                      ownership);
                None
            }
        },
    }
}

/// Identify a block device in the context where a udev event has been
/// captured for some block device. Return None if the device does not
/// appear to be a Stratis device. Log at an appropriate level on all errors.
pub fn identify_block_device(dev: &libudev::Device) -> Option<StratisInfo> {
    let initialized = dev.is_initialized();
    if !initialized {
        debug!("Found a udev entry for a device identified as a block device, but udev also identified it as uninitialized, disregarding the device");
        return None;
    };

    match decide_ownership(dev) {
        Err(err) => {
            warn!(
                "Could not determine ownership of a udev block device, disregarding the device: {}",
                err
            );
            None
        }
        Ok(ownership) => match ownership {
            UdevOwnership::Stratis => process_stratis_device(dev),
            UdevOwnership::Unowned => process_unowned_device(dev),
            _ => None,
        },
    }
}

/// Retrieve all block devices that should be made use of by the
/// Stratis engine. This excludes Stratis block devices that appear to be
/// multipath members.
///
/// Includes a fallback path, which is used if no Stratis block devices are
/// found using the obvious udev property- and enumerator-based approach.
/// This fallback path is more expensive, because it must search all block
/// devices via udev rather than just all Stratis block devices.
///
/// Omits any device that appears problematic in some way.
///
/// Return an error only on a failure to construct or scan with a udev
/// enumerator.
///
/// Returns a map of pool uuids to a map of devices to devnodes for each pool.
pub fn find_all() -> libudev::Result<HashMap<PoolUuid, HashMap<Device, PathBuf>>> {
    info!("Beginning initial search for Stratis block devices");
    let pool_map = find_all_stratis_devices()?;

    if pool_map.is_empty() {
        // No Stratis devices have been found, possible reasons are:
        // 1. There are none
        // 2. There are many incomplete udev entries, because this code is
        // being run before the udev database is populated.
        //
        // Try again to find Stratis block devices, but this time enumerate
        // all block devices, not just all the ones that can be identified
        // as Stratis blockdevs by udev, and process only those that appear
        // unclaimed or appear to be claimed by Stratis (and not
        // multipath members).

        info!("Could not identify any Stratis devices by a udev search for devices with ID_FS_TYPE=\"stratis\"; using fallback search mechanism");

        find_all_block_devices_with_stratis_signatures()
    } else {
        Ok(pool_map)
    }
}
<|endoftext|>


<commit_before>use super::*;
use crate::test;
use envmnt;

#[test]
#[ignore]
fn get_cargo_home_env() {
    let directory_path = env::current_dir().unwrap();
    let directory = directory_path.to_str().unwrap();
    envmnt::set("CARGO_HOME", directory);

    let output = get_cargo_home().unwrap();
    envmnt::remove("CARGO_HOME");

    assert_eq!(output, directory);
}

#[test]
#[ignore]
fn get_cargo_home_no_env() {
    envmnt::remove("CARGO_HOME");

    let output = get_cargo_home().unwrap();

    assert!(output.contains(".cargo"));
}

#[test]
#[ignore]
fn get_cargo_home_file() {
    let directory_path = env::current_dir().unwrap();
    let directory = directory_path.to_str().unwrap();
    let file_path = Path::new(directory).join("Cargo.toml");
    envmnt::set("CARGO_HOME", file_path);

    let output = get_cargo_home();
    envmnt::remove("CARGO_HOME");

    assert!(output.is_none());
}

#[test]
fn load_crates_toml_valid() {
    let cwd = envmnt::get_or_panic("CARGO_MAKE_WORKING_DIRECTORY");
    let file_path = Path::new(&cwd).join("src/lib/test/test_files");
    let directory = file_path.to_str().unwrap();
    let info_option = load_crates_toml(&directory);

    assert!(info_option.is_some());
    let info = info_option.unwrap();
    assert!(info.v1.is_some());
    assert_eq!(info.v1.unwrap().len(), 3);
}

#[test]
fn load_crates_toml_file_not_found() {
    let cwd = envmnt::get_or_panic("CARGO_MAKE_WORKING_DIRECTORY");
    let info_option = load_crates_toml(&cwd);

    assert!(info_option.is_none());
}

#[test]
fn get_crate_version_from_info_no_info() {
    let info = CratesRegistryInfo { v1: None };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_empty_info() {
    let info = CratesRegistryInfo {
        v1: Some(HashMap::new()),
    };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_not_found() {
    let mut map = HashMap::new();
    map.insert("1 1 1".to_string(), vec![]);
    map.insert("2 2 2".to_string(), vec![]);
    let info = CratesRegistryInfo { v1: Some(map) };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_found_invalid_line() {
    let mut map = HashMap::new();
    map.insert("1 1 1".to_string(), vec![]);
    map.insert("cargo-make".to_string(), vec![]);
    map.insert("2 2 2".to_string(), vec![]);
    let info = CratesRegistryInfo { v1: Some(map) };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_found_invalid_version() {
    let mut map = HashMap::new();
    map.insert("1 1 1".to_string(), vec![]);
    map.insert("cargo-make abc 2".to_string(), vec![]);
    map.insert("2 2 2".to_string(), vec![]);
    let info = CratesRegistryInfo { v1: Some(map) };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_valid() {
    let mut map = HashMap::new();
    map.insert("1 1 1".to_string(), vec![]);
    map.insert("cargo-make 1.2.3 2".to_string(), vec![]);
    map.insert("2 2 2".to_string(), vec![]);
    let info = CratesRegistryInfo { v1: Some(map) };
    let version = get_crate_version_from_info("cargo-make", &info);

    let semver_version = version.unwrap();
    assert_eq!(semver_version.major, 1);
    assert_eq!(semver_version.minor, 2);
    assert_eq!(semver_version.patch, 3);
}

#[test]
#[cfg(target_os = "linux")]
fn get_crate_version_for_rustup_component() {
    if test::is_rust_channel(RustChannel::Stable) {
        let mut version = get_crate_version("rustfmt", None);
        assert!(version.is_none());

        version = get_crate_version("rustfmt", Some("rustfmt"));
        assert!(version.is_some());
    }
}

#[test]
fn is_min_version_valid_for_versions_equal() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_min_version_valid_for_versions_false_major() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("2.2.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_min_version_valid_for_versions_false_minor() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.3.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_min_version_valid_for_versions_false_patch() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.4").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_min_version_valid_for_versions_true_major() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("2.2.3").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_min_version_valid_for_versions_true_minor() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("1.3.3").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_min_version_valid_for_versions_true_patch() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("1.2.4").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_min_version_valid_not_found() {
    let valid = is_min_version_valid("bad_crate", "1.2.3", None);

    assert!(valid);
}

#[test]
fn is_min_version_valid_invalid_version() {
    let valid = is_min_version_valid("cargo-make", "bad_version", None);

    assert!(valid);
}

#[test]
fn is_min_version_valid_old_version() {
    let valid = is_min_version_valid("cargo-make", "0.0.1", None);

    assert!(valid);
}

#[test]
fn is_min_version_valid_newer_version() {
    let valid = is_min_version_valid("cargo-make", "10000.0.0", None);

    assert!(!valid);
}

#[test]
fn is_min_version_valid_same_version() {
    let version = get_crate_version("cargo-make", None).unwrap();
    let mut version_string = String::new();
    version_string.push_str(&version.major.to_string());
    version_string.push_str(".");
    version_string.push_str(&version.minor.to_string());
    version_string.push_str(".");
    version_string.push_str(&version.patch.to_string());

    let valid = is_min_version_valid("cargo-make", &version_string, None);

    assert!(valid);
}

#[test]
fn is_version_valid_for_versions_equal() {
    let valid = is_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_version_valid_for_versions_false_major() {
    let valid = is_version_valid_for_versions(
        &Version::parse("2.2.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_version_valid_for_versions_false_minor() {
    let valid = is_version_valid_for_versions(
        &Version::parse("1.3.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_version_valid_for_versions_false_patch() {
    let valid = is_version_valid_for_versions(
        &Version::parse("1.2.4").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_version_valid_not_found() {
    let valid = is_version_valid("bad_crate", "1.2.3", None);

    assert!(valid);
}

#[test]
fn is_version_valid_invalid_version() {
    let valid = is_version_valid("cargo-make", "bad_version", None);

    assert!(valid);
}

#[test]
fn is_version_valid_old_version() {
    let valid = is_version_valid("cargo-make", "0.0.1", None);

    assert!(!valid);
}

#[test]
fn is_version_valid_newer_version() {
    let valid = is_version_valid("cargo-make", "10000.0.0", None);

    assert!(!valid);
}

#[test]
fn is_version_valid_same_version() {
    let version = get_crate_version("cargo-make", None).unwrap();
    let mut version_string = String::new();
    version_string.push_str(&version.major.to_string());
    version_string.push_str(".");
    version_string.push_str(&version.minor.to_string());
    version_string.push_str(".");
    version_string.push_str(&version.patch.to_string());

    let valid = is_min_version_valid("cargo-make", &version_string, None);

    assert!(valid);
}
<commit_msg>fix tests<commit_after>use super::*;
use crate::test;
use envmnt;
use rust_info::types::RustChannel;

#[test]
#[ignore]
fn get_cargo_home_env() {
    let directory_path = env::current_dir().unwrap();
    let directory = directory_path.to_str().unwrap();
    envmnt::set("CARGO_HOME", directory);

    let output = get_cargo_home().unwrap();
    envmnt::remove("CARGO_HOME");

    assert_eq!(output, directory);
}

#[test]
#[ignore]
fn get_cargo_home_no_env() {
    envmnt::remove("CARGO_HOME");

    let output = get_cargo_home().unwrap();

    assert!(output.contains(".cargo"));
}

#[test]
#[ignore]
fn get_cargo_home_file() {
    let directory_path = env::current_dir().unwrap();
    let directory = directory_path.to_str().unwrap();
    let file_path = Path::new(directory).join("Cargo.toml");
    envmnt::set("CARGO_HOME", file_path);

    let output = get_cargo_home();
    envmnt::remove("CARGO_HOME");

    assert!(output.is_none());
}

#[test]
fn load_crates_toml_valid() {
    let cwd = envmnt::get_or_panic("CARGO_MAKE_WORKING_DIRECTORY");
    let file_path = Path::new(&cwd).join("src/lib/test/test_files");
    let directory = file_path.to_str().unwrap();
    let info_option = load_crates_toml(&directory);

    assert!(info_option.is_some());
    let info = info_option.unwrap();
    assert!(info.v1.is_some());
    assert_eq!(info.v1.unwrap().len(), 3);
}

#[test]
fn load_crates_toml_file_not_found() {
    let cwd = envmnt::get_or_panic("CARGO_MAKE_WORKING_DIRECTORY");
    let info_option = load_crates_toml(&cwd);

    assert!(info_option.is_none());
}

#[test]
fn get_crate_version_from_info_no_info() {
    let info = CratesRegistryInfo { v1: None };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_empty_info() {
    let info = CratesRegistryInfo {
        v1: Some(HashMap::new()),
    };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_not_found() {
    let mut map = HashMap::new();
    map.insert("1 1 1".to_string(), vec![]);
    map.insert("2 2 2".to_string(), vec![]);
    let info = CratesRegistryInfo { v1: Some(map) };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_found_invalid_line() {
    let mut map = HashMap::new();
    map.insert("1 1 1".to_string(), vec![]);
    map.insert("cargo-make".to_string(), vec![]);
    map.insert("2 2 2".to_string(), vec![]);
    let info = CratesRegistryInfo { v1: Some(map) };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_found_invalid_version() {
    let mut map = HashMap::new();
    map.insert("1 1 1".to_string(), vec![]);
    map.insert("cargo-make abc 2".to_string(), vec![]);
    map.insert("2 2 2".to_string(), vec![]);
    let info = CratesRegistryInfo { v1: Some(map) };
    let version = get_crate_version_from_info("cargo-make", &info);

    assert!(version.is_none());
}

#[test]
fn get_crate_version_from_info_valid() {
    let mut map = HashMap::new();
    map.insert("1 1 1".to_string(), vec![]);
    map.insert("cargo-make 1.2.3 2".to_string(), vec![]);
    map.insert("2 2 2".to_string(), vec![]);
    let info = CratesRegistryInfo { v1: Some(map) };
    let version = get_crate_version_from_info("cargo-make", &info);

    let semver_version = version.unwrap();
    assert_eq!(semver_version.major, 1);
    assert_eq!(semver_version.minor, 2);
    assert_eq!(semver_version.patch, 3);
}

#[test]
#[cfg(target_os = "linux")]
fn get_crate_version_for_rustup_component() {
    if test::is_rust_channel(RustChannel::Stable) {
        let mut version = get_crate_version("rustfmt", None);
        assert!(version.is_none());

        version = get_crate_version("rustfmt", Some("rustfmt"));
        assert!(version.is_some());
    }
}

#[test]
fn is_min_version_valid_for_versions_equal() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_min_version_valid_for_versions_false_major() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("2.2.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_min_version_valid_for_versions_false_minor() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.3.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_min_version_valid_for_versions_false_patch() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.4").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_min_version_valid_for_versions_true_major() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("2.2.3").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_min_version_valid_for_versions_true_minor() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("1.3.3").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_min_version_valid_for_versions_true_patch() {
    let valid = is_min_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("1.2.4").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_min_version_valid_not_found() {
    let valid = is_min_version_valid("bad_crate", "1.2.3", None);

    assert!(valid);
}

#[test]
fn is_min_version_valid_invalid_version() {
    let valid = is_min_version_valid("cargo-make", "bad_version", None);

    assert!(valid);
}

#[test]
fn is_min_version_valid_old_version() {
    let valid = is_min_version_valid("cargo-make", "0.0.1", None);

    assert!(valid);
}

#[test]
fn is_min_version_valid_newer_version() {
    let valid = is_min_version_valid("cargo-make", "10000.0.0", None);

    assert!(!valid);
}

#[test]
fn is_min_version_valid_same_version() {
    let version = get_crate_version("cargo-make", None).unwrap();
    let mut version_string = String::new();
    version_string.push_str(&version.major.to_string());
    version_string.push_str(".");
    version_string.push_str(&version.minor.to_string());
    version_string.push_str(".");
    version_string.push_str(&version.patch.to_string());

    let valid = is_min_version_valid("cargo-make", &version_string, None);

    assert!(valid);
}

#[test]
fn is_version_valid_for_versions_equal() {
    let valid = is_version_valid_for_versions(
        &Version::parse("1.2.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(valid);
}

#[test]
fn is_version_valid_for_versions_false_major() {
    let valid = is_version_valid_for_versions(
        &Version::parse("2.2.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_version_valid_for_versions_false_minor() {
    let valid = is_version_valid_for_versions(
        &Version::parse("1.3.3").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_version_valid_for_versions_false_patch() {
    let valid = is_version_valid_for_versions(
        &Version::parse("1.2.4").unwrap(),
        &Version::parse("1.2.3").unwrap(),
    );

    assert!(!valid);
}

#[test]
fn is_version_valid_not_found() {
    let valid = is_version_valid("bad_crate", "1.2.3", None);

    assert!(valid);
}

#[test]
fn is_version_valid_invalid_version() {
    let valid = is_version_valid("cargo-make", "bad_version", None);

    assert!(valid);
}

#[test]
fn is_version_valid_old_version() {
    let valid = is_version_valid("cargo-make", "0.0.1", None);

    assert!(!valid);
}

#[test]
fn is_version_valid_newer_version() {
    let valid = is_version_valid("cargo-make", "10000.0.0", None);

    assert!(!valid);
}

#[test]
fn is_version_valid_same_version() {
    let version = get_crate_version("cargo-make", None).unwrap();
    let mut version_string = String::new();
    version_string.push_str(&version.major.to_string());
    version_string.push_str(".");
    version_string.push_str(&version.minor.to_string());
    version_string.push_str(".");
    version_string.push_str(&version.patch.to_string());

    let valid = is_min_version_valid("cargo-make", &version_string, None);

    assert!(valid);
}
<|endoftext|>


<commit_before>// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! The Rust compiler.
//!
//! # Note
//!
//! This API is completely unstable and subject to change.

#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
      html_favicon_url = "https://doc.rust-lang.org/favicon.ico",
      html_root_url = "https://doc.rust-lang.org/nightly/")]
#![deny(warnings)]

#![feature(box_syntax)]

use std::any::Any;
use std::io::{self, Write};
use std::fs::File;
use std::path::Path;
use std::sync::{mpsc, Arc};

use rustc_data_structures::owning_ref::OwningRef;
use rustc_data_structures::sync::Lrc;
use flate2::Compression;
use flate2::write::DeflateEncoder;

use syntax::symbol::Symbol;
use rustc::hir::def_id::LOCAL_CRATE;
use rustc::session::{Session, CompileIncomplete};
use rustc::session::config::{CrateType, OutputFilenames, PrintRequest};
use rustc::ty::TyCtxt;
use rustc::ty::query::Providers;
use rustc::middle::cstore::EncodedMetadata;
use rustc::middle::cstore::MetadataLoader;
use rustc::dep_graph::DepGraph;
use rustc_target::spec::Target;
use rustc_mir::monomorphize::collector;
use link::{build_link_meta, out_filename};

pub use rustc_data_structures::sync::MetadataRef;

pub trait CodegenBackend {
    fn init(&self, _sess: &Session) {}
    fn print(&self, _req: PrintRequest, _sess: &Session) {}
    fn target_features(&self, _sess: &Session) -> Vec<Symbol> { vec![] }
    fn print_passes(&self) {}
    fn print_version(&self) {}
    fn diagnostics(&self) -> &[(&'static str, &'static str)] { &[] }

    fn metadata_loader(&self) -> Box<MetadataLoader + Sync>;
    fn provide(&self, _providers: &mut Providers);
    fn provide_extern(&self, _providers: &mut Providers);
    fn codegen_crate<'a, 'tcx>(
        &self,
        tcx: TyCtxt<'a, 'tcx, 'tcx>,
        rx: mpsc::Receiver<Box<Any + Send>>
    ) -> Box<Any>;

    /// This is called on the returned `Box<Any>` from `codegen_backend`
    ///
    /// # Panics
    ///
    /// Panics when the passed `Box<Any>` was not returned by `codegen_backend`.
    fn join_codegen_and_link(
        &self,
        ongoing_codegen: Box<Any>,
        sess: &Session,
        dep_graph: &DepGraph,
        outputs: &OutputFilenames,
    ) -> Result<(), CompileIncomplete>;
}

pub struct NoLlvmMetadataLoader;

impl MetadataLoader for NoLlvmMetadataLoader {
    fn get_rlib_metadata(&self, _: &Target, filename: &Path) -> Result<MetadataRef, String> {
        let mut file = File::open(filename)
            .map_err(|e| format!("metadata file open err: {:?}", e))?;

        let mut buf = Vec::new();
        io::copy(&mut file, &mut buf).unwrap();
        let buf: OwningRef<Vec<u8>, [u8]> = OwningRef::new(buf).into();
        return Ok(rustc_erase_owner!(buf.map_owner_box()));
    }

    fn get_dylib_metadata(&self, target: &Target, filename: &Path) -> Result<MetadataRef, String> {
        self.get_rlib_metadata(target, filename)
    }
}

pub struct MetadataOnlyCodegenBackend(());
pub struct OngoingCodegen {
    metadata: EncodedMetadata,
    metadata_version: Vec<u8>,
    crate_name: Symbol,
}

impl MetadataOnlyCodegenBackend {
    pub fn new() -> Box<CodegenBackend> {
        box MetadataOnlyCodegenBackend(())
    }
}

impl CodegenBackend for MetadataOnlyCodegenBackend {
    fn init(&self, sess: &Session) {
        for cty in sess.opts.crate_types.iter() {
            match *cty {
                CrateType::CrateTypeRlib | CrateType::CrateTypeDylib |
                CrateType::CrateTypeExecutable => {},
                _ => {
                    sess.parse_sess.span_diagnostic.warn(
                        &format!("LLVM unsupported, so output type {} is not supported", cty)
                    );
                },
            }
        }
    }

    fn metadata_loader(&self) -> Box<MetadataLoader + Sync> {
        box NoLlvmMetadataLoader
    }

    fn provide(&self, providers: &mut Providers) {
        ::symbol_names::provide(providers);

        providers.target_features_whitelist = |_tcx, _cnum| {
            Lrc::new(::llvm_target_features::all_known_features()
                .map(|(a, b)| (a.to_string(), b.map(|s| s.to_string())))
                .collect())
        };
        providers.is_reachable_non_generic = |_tcx, _defid| true;
        providers.exported_symbols = |_tcx, _crate| Arc::new(Vec::new());
        providers.wasm_custom_sections = |_tcx, _crate| Lrc::new(Vec::new());
    }
    fn provide_extern(&self, providers: &mut Providers) {
        providers.is_reachable_non_generic = |_tcx, _defid| true;
    }

    fn codegen_crate<'a, 'tcx>(
        &self,
        tcx: TyCtxt<'a, 'tcx, 'tcx>,
        _rx: mpsc::Receiver<Box<Any + Send>>
    ) -> Box<Any> {
        use rustc_mir::monomorphize::item::MonoItem;

        ::check_for_rustc_errors_attr(tcx);
        ::symbol_names_test::report_symbol_names(tcx);
        ::rustc_incremental::assert_dep_graph(tcx);
        ::rustc_incremental::assert_module_sources::assert_module_sources(tcx);
        ::rustc_mir::monomorphize::assert_symbols_are_distinct(tcx,
            collector::collect_crate_mono_items(
                tcx,
                collector::MonoItemCollectionMode::Eager
            ).0.iter()
        );
        //::rustc::middle::dependency_format::calculate(tcx);
        let _ = tcx.link_args(LOCAL_CRATE);
        let _ = tcx.native_libraries(LOCAL_CRATE);
        for mono_item in
            collector::collect_crate_mono_items(
                tcx,
                collector::MonoItemCollectionMode::Eager
            ).0 {
            match mono_item {
                MonoItem::Fn(inst) => {
                    let def_id = inst.def_id();
                    if def_id.is_local()  {
                        let _ = inst.def.is_inline(tcx);
                        let _ = tcx.codegen_fn_attrs(def_id);
                    }
                }
                _ => {}
            }
        }
        tcx.sess.abort_if_errors();

        let link_meta = build_link_meta(tcx.crate_hash(LOCAL_CRATE));
        let metadata = tcx.encode_metadata(&link_meta);

        box OngoingCodegen {
            metadata: metadata,
            metadata_version: tcx.metadata_encoding_version().to_vec(),
            crate_name: tcx.crate_name(LOCAL_CRATE),
        }
    }

    fn join_codegen_and_link(
        &self,
        ongoing_codegen: Box<Any>,
        sess: &Session,
        _dep_graph: &DepGraph,
        outputs: &OutputFilenames,
    ) -> Result<(), CompileIncomplete> {
        let ongoing_codegen = ongoing_codegen.downcast::<OngoingCodegen>()
            .expect("Expected MetadataOnlyCodegenBackend's OngoingCodegen, found Box<Any>");
        for &crate_type in sess.opts.crate_types.iter() {
            if crate_type != CrateType::CrateTypeRlib && crate_type != CrateType::CrateTypeDylib {
                continue;
            }
            let output_name =
                out_filename(sess, crate_type, &outputs, &ongoing_codegen.crate_name.as_str());
            let mut compressed = ongoing_codegen.metadata_version.clone();
            let metadata = if crate_type == CrateType::CrateTypeDylib {
                DeflateEncoder::new(&mut compressed, Compression::fast())
                    .write_all(&ongoing_codegen.metadata.raw_data)
                    .unwrap();
                &compressed
            } else {
                &ongoing_codegen.metadata.raw_data
            };
            let mut file = File::create(&output_name).unwrap();
            file.write_all(metadata).unwrap();
        }

        sess.abort_if_errors();
        if !sess.opts.crate_types.contains(&CrateType::CrateTypeRlib)
            && !sess.opts.crate_types.contains(&CrateType::CrateTypeDylib)
        {
            sess.fatal("Executables are not supported by the metadata-only backend.");
        }
        Ok(())
    }
}
<commit_msg>Leave fixme comment<commit_after>// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! The Rust compiler.
//!
//! # Note
//!
//! This API is completely unstable and subject to change.

#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
      html_favicon_url = "https://doc.rust-lang.org/favicon.ico",
      html_root_url = "https://doc.rust-lang.org/nightly/")]
#![deny(warnings)]

#![feature(box_syntax)]

use std::any::Any;
use std::io::{self, Write};
use std::fs::File;
use std::path::Path;
use std::sync::{mpsc, Arc};

use rustc_data_structures::owning_ref::OwningRef;
use rustc_data_structures::sync::Lrc;
use flate2::Compression;
use flate2::write::DeflateEncoder;

use syntax::symbol::Symbol;
use rustc::hir::def_id::LOCAL_CRATE;
use rustc::session::{Session, CompileIncomplete};
use rustc::session::config::{CrateType, OutputFilenames, PrintRequest};
use rustc::ty::TyCtxt;
use rustc::ty::query::Providers;
use rustc::middle::cstore::EncodedMetadata;
use rustc::middle::cstore::MetadataLoader;
use rustc::dep_graph::DepGraph;
use rustc_target::spec::Target;
use rustc_mir::monomorphize::collector;
use link::{build_link_meta, out_filename};

pub use rustc_data_structures::sync::MetadataRef;

pub trait CodegenBackend {
    fn init(&self, _sess: &Session) {}
    fn print(&self, _req: PrintRequest, _sess: &Session) {}
    fn target_features(&self, _sess: &Session) -> Vec<Symbol> { vec![] }
    fn print_passes(&self) {}
    fn print_version(&self) {}
    fn diagnostics(&self) -> &[(&'static str, &'static str)] { &[] }

    fn metadata_loader(&self) -> Box<MetadataLoader + Sync>;
    fn provide(&self, _providers: &mut Providers);
    fn provide_extern(&self, _providers: &mut Providers);
    fn codegen_crate<'a, 'tcx>(
        &self,
        tcx: TyCtxt<'a, 'tcx, 'tcx>,
        rx: mpsc::Receiver<Box<Any + Send>>
    ) -> Box<Any>;

    /// This is called on the returned `Box<Any>` from `codegen_backend`
    ///
    /// # Panics
    ///
    /// Panics when the passed `Box<Any>` was not returned by `codegen_backend`.
    fn join_codegen_and_link(
        &self,
        ongoing_codegen: Box<Any>,
        sess: &Session,
        dep_graph: &DepGraph,
        outputs: &OutputFilenames,
    ) -> Result<(), CompileIncomplete>;
}

pub struct NoLlvmMetadataLoader;

impl MetadataLoader for NoLlvmMetadataLoader {
    fn get_rlib_metadata(&self, _: &Target, filename: &Path) -> Result<MetadataRef, String> {
        let mut file = File::open(filename)
            .map_err(|e| format!("metadata file open err: {:?}", e))?;

        let mut buf = Vec::new();
        io::copy(&mut file, &mut buf).unwrap();
        let buf: OwningRef<Vec<u8>, [u8]> = OwningRef::new(buf).into();
        return Ok(rustc_erase_owner!(buf.map_owner_box()));
    }

    fn get_dylib_metadata(&self, target: &Target, filename: &Path) -> Result<MetadataRef, String> {
        self.get_rlib_metadata(target, filename)
    }
}

pub struct MetadataOnlyCodegenBackend(());
pub struct OngoingCodegen {
    metadata: EncodedMetadata,
    metadata_version: Vec<u8>,
    crate_name: Symbol,
}

impl MetadataOnlyCodegenBackend {
    pub fn new() -> Box<CodegenBackend> {
        box MetadataOnlyCodegenBackend(())
    }
}

impl CodegenBackend for MetadataOnlyCodegenBackend {
    fn init(&self, sess: &Session) {
        for cty in sess.opts.crate_types.iter() {
            match *cty {
                CrateType::CrateTypeRlib | CrateType::CrateTypeDylib |
                CrateType::CrateTypeExecutable => {},
                _ => {
                    sess.parse_sess.span_diagnostic.warn(
                        &format!("LLVM unsupported, so output type {} is not supported", cty)
                    );
                },
            }
        }
    }

    fn metadata_loader(&self) -> Box<MetadataLoader + Sync> {
        box NoLlvmMetadataLoader
    }

    fn provide(&self, providers: &mut Providers) {
        ::symbol_names::provide(providers);

        providers.target_features_whitelist = |_tcx, _cnum| {
            Lrc::new(::llvm_target_features::all_known_features()
                .map(|(a, b)| (a.to_string(), b.map(|s| s.to_string())))
                .collect())
        };
        providers.is_reachable_non_generic = |_tcx, _defid| true;
        providers.exported_symbols = |_tcx, _crate| Arc::new(Vec::new());
        providers.wasm_custom_sections = |_tcx, _crate| Lrc::new(Vec::new());
    }
    fn provide_extern(&self, providers: &mut Providers) {
        providers.is_reachable_non_generic = |_tcx, _defid| true;
    }

    fn codegen_crate<'a, 'tcx>(
        &self,
        tcx: TyCtxt<'a, 'tcx, 'tcx>,
        _rx: mpsc::Receiver<Box<Any + Send>>
    ) -> Box<Any> {
        use rustc_mir::monomorphize::item::MonoItem;

        ::check_for_rustc_errors_attr(tcx);
        ::symbol_names_test::report_symbol_names(tcx);
        ::rustc_incremental::assert_dep_graph(tcx);
        ::rustc_incremental::assert_module_sources::assert_module_sources(tcx);
        ::rustc_mir::monomorphize::assert_symbols_are_distinct(tcx,
            collector::collect_crate_mono_items(
                tcx,
                collector::MonoItemCollectionMode::Eager
            ).0.iter()
        );
        // FIXME: Fix this
        // ::rustc::middle::dependency_format::calculate(tcx);
        let _ = tcx.link_args(LOCAL_CRATE);
        let _ = tcx.native_libraries(LOCAL_CRATE);
        for mono_item in
            collector::collect_crate_mono_items(
                tcx,
                collector::MonoItemCollectionMode::Eager
            ).0 {
            match mono_item {
                MonoItem::Fn(inst) => {
                    let def_id = inst.def_id();
                    if def_id.is_local()  {
                        let _ = inst.def.is_inline(tcx);
                        let _ = tcx.codegen_fn_attrs(def_id);
                    }
                }
                _ => {}
            }
        }
        tcx.sess.abort_if_errors();

        let link_meta = build_link_meta(tcx.crate_hash(LOCAL_CRATE));
        let metadata = tcx.encode_metadata(&link_meta);

        box OngoingCodegen {
            metadata: metadata,
            metadata_version: tcx.metadata_encoding_version().to_vec(),
            crate_name: tcx.crate_name(LOCAL_CRATE),
        }
    }

    fn join_codegen_and_link(
        &self,
        ongoing_codegen: Box<Any>,
        sess: &Session,
        _dep_graph: &DepGraph,
        outputs: &OutputFilenames,
    ) -> Result<(), CompileIncomplete> {
        let ongoing_codegen = ongoing_codegen.downcast::<OngoingCodegen>()
            .expect("Expected MetadataOnlyCodegenBackend's OngoingCodegen, found Box<Any>");
        for &crate_type in sess.opts.crate_types.iter() {
            if crate_type != CrateType::CrateTypeRlib && crate_type != CrateType::CrateTypeDylib {
                continue;
            }
            let output_name =
                out_filename(sess, crate_type, &outputs, &ongoing_codegen.crate_name.as_str());
            let mut compressed = ongoing_codegen.metadata_version.clone();
            let metadata = if crate_type == CrateType::CrateTypeDylib {
                DeflateEncoder::new(&mut compressed, Compression::fast())
                    .write_all(&ongoing_codegen.metadata.raw_data)
                    .unwrap();
                &compressed
            } else {
                &ongoing_codegen.metadata.raw_data
            };
            let mut file = File::create(&output_name).unwrap();
            file.write_all(metadata).unwrap();
        }

        sess.abort_if_errors();
        if !sess.opts.crate_types.contains(&CrateType::CrateTypeRlib)
            && !sess.opts.crate_types.contains(&CrateType::CrateTypeDylib)
        {
            sess.fatal("Executables are not supported by the metadata-only backend.");
        }
        Ok(())
    }
}
<|endoftext|>