<commit_before>//! Monitor progress at a `Stream`.

use std::rc::Rc;
use std::cell::RefCell;

use progress::{Timestamp, Operate, Antichain};
use progress::frontier::MutableAntichain;
use progress::nested::subgraph::{Source, Target};
use progress::count_map::CountMap;

use dataflow::channels::pushers::Tee;
use dataflow::channels::pushers::Counter as PushCounter;
use dataflow::channels::pushers::buffer::Buffer as PushBuffer;
use dataflow::channels::pact::{ParallelizationContract, Pipeline};
use dataflow::channels::pullers::Counter as PullCounter;

use Data;
use dataflow::{Stream, Scope};

/// Monitors progress at a `Stream`.
pub trait Probe<G: Scope, D: Data> {
    /// Constructs a progress probe which indicates which timestamps have elapsed at the operator.
    ///
    /// #Examples
    /// ```
    /// use timely::*;
    /// use timely::dataflow::Scope;
    /// use timely::dataflow::operators::{Input, Probe, Inspect};
    /// use timely::progress::timestamp::RootTimestamp;
    ///
    /// // construct and execute a timely dataflow
    /// timely::execute(Configuration::Thread, |worker| {
    ///
    ///     // add an input and base computation off of it
    ///     let (mut input, probe) = worker.dataflow(|scope| {
    ///         let (input, stream) = scope.new_input();
    ///         let probe = stream.inspect(|x| println!("hello {:?}", x))
    ///                           .probe();
    ///         (input, probe)
    ///     });
    ///
    ///     // introduce input, advance computation
    ///     for round in 0..10 {
    ///         input.send(round);
    ///         input.advance_to(round + 1);
    ///         worker.step_while(|| probe.less_than(input.time()));
    ///     }
    /// }).unwrap();
    /// ```
    fn probe(&self) -> Handle<G::Timestamp>;

    /// Inserts a progress probe in a stream.
    ///
    /// #Examples
    /// ```
    /// use timely::*;
    /// use timely::dataflow::Scope;
    /// use timely::dataflow::operators::{Input, Probe, Inspect};
    /// use timely::dataflow::operators::probe::Handle;
    /// use timely::progress::timestamp::RootTimestamp;
    ///
    /// // construct and execute a timely dataflow
    /// timely::execute(Configuration::Thread, |worker| {
    ///
    ///     // add an input and base computation off of it
    ///     let mut probe = Handle::new();
    ///     let mut input = worker.dataflow(|scope| {
    ///         let (input, stream) = scope.new_input();
    ///         stream.probe_with(&mut probe)
    ///               .inspect(|x| println!("hello {:?}", x));
    ///
    ///         input
    ///     });
    ///
    ///     // introduce input, advance computation
    ///     for round in 0..10 {
    ///         input.send(round);
    ///         input.advance_to(round + 1);
    ///         worker.step_while(|| probe.less_than(input.time()));
    ///     }
    /// }).unwrap();
    /// ```
    fn probe_with(&self, handle: &mut Handle<G::Timestamp>) -> Stream<G, D>;
}

impl<G: Scope, D: Data> Probe<G, D> for Stream<G, D> {
    fn probe(&self) -> Handle<G::Timestamp> {

        // the frontier is shared state; scope updates, handle reads.
        let handle = Handle::new();

        let mut scope = self.scope();   // clones the scope
        let channel_id = scope.new_identifier();

        let (sender, receiver) = Pipeline.connect(&mut scope, channel_id);
        let (targets, registrar) = Tee::<G::Timestamp,D>::new();
        let operator = Operator {
            input: PullCounter::new(receiver),
            output: PushBuffer::new(PushCounter::new(targets, Rc::new(RefCell::new(CountMap::new())))),
            frontier: handle.frontier.clone(),
        };

        let index = scope.add_operator(operator);
        self.connect_to(Target { index: index, port: 0 }, sender, channel_id);
        Stream::new(Source { index: index, port: 0 }, registrar, scope);
        handle
    }
    fn probe_with(&self, handle: &mut Handle<G::Timestamp>) -> Stream<G, D> {

        let mut scope = self.scope();   // clones the scope
        let channel_id = scope.new_identifier();

        let (sender, receiver) = Pipeline.connect(&mut scope, channel_id);
        let (targets, registrar) = Tee::<G::Timestamp,D>::new();
        let operator = Operator {
            input: PullCounter::new(receiver),
            output: PushBuffer::new(PushCounter::new(targets, Rc::new(RefCell::new(CountMap::new())))),
            frontier: handle.frontier.clone(),
        };

        let index = scope.add_operator(operator);
        self.connect_to(Target { index: index, port: 0 }, sender, channel_id);
        Stream::new(Source { index: index, port: 0 }, registrar, scope)
    }
}

/// Reports information about progress at the probe.
pub struct Handle<T:Timestamp> {
    frontier: Rc<RefCell<MutableAntichain<T>>>
}

impl<T: Timestamp> Handle<T> {
    /// returns true iff the frontier is strictly less than `time`.
    #[inline] pub fn less_than(&self, time: &T) -> bool { self.frontier.borrow().less_than(time) }
    /// returns true iff the frontier is less than or equal to `time`.
    #[inline] pub fn less_equal(&self, time: &T) -> bool { self.frontier.borrow().less_equal(time) }
    /// returns true iff the frontier is empty.
    #[inline] pub fn done(&self) -> bool { self.frontier.borrow().elements().len() == 0 }
    /// Allocates a new handle.
    #[inline] pub fn new() -> Self { Handle { frontier: Rc::new(RefCell::new(MutableAntichain::new())) } }
}

impl<T: Timestamp> Clone for Handle<T> {
    fn clone(&self) -> Self {
        Handle {
            frontier: self.frontier.clone()
        }
    }
}

struct Operator<T:Timestamp, D: Data> {
    input: PullCounter<T, D>,
    output: PushBuffer<T, D, PushCounter<T, D, Tee<T, D>>>,
    frontier: Rc<RefCell<MutableAntichain<T>>>
}

impl<T:Timestamp, D: Data> Operate<T> for Operator<T, D> {
    fn name(&self) -> String { "Probe".to_owned() }
    fn inputs(&self) -> usize { 1 }
    fn outputs(&self) -> usize { 1 }

    // we need to set the initial value of the frontier
    fn set_external_summary(&mut self, _: Vec<Vec<Antichain<T::Summary>>>, counts: &mut [CountMap<T>]) {
        let mut borrow = self.frontier.borrow_mut();
        while let Some((time, delta)) = counts[0].pop() {
            borrow.update(&time, delta);
        }
    }

    // each change to the frontier should be shared
    fn push_external_progress(&mut self, counts: &mut [CountMap<T>]) {
        let mut borrow = self.frontier.borrow_mut();
        while let Some((time, delta)) = counts[0].pop() {
            borrow.update(&time, delta);
        }
    }

    // the scope does nothing. this is actually a problem, because "reachability" assumes all messages on each edge.
    fn pull_internal_progress(&mut self, consumed: &mut [CountMap<T>], _: &mut [CountMap<T>], produced: &mut [CountMap<T>]) -> bool {

        while let Some((time, data)) = self.input.next() {
            self.output.session(time).give_content(data);
        }

        self.output.cease();

        // extract what we know about progress from the input and output adapters.
        self.input.pull_progress(&mut consumed[0]);
        self.output.inner().pull_progress(&mut produced[0]);

        false
    }
}

#[cfg(test)]
mod tests {

    use ::Configuration;
    use ::progress::timestamp::RootTimestamp;
    use dataflow::operators::{Input, Probe};

    #[test]
    fn probe() {

        // initializes and runs a timely dataflow computation
        ::execute(Configuration::Thread, |worker| {

            // create a new input, and inspect its output
            let (mut input, probe) = worker.dataflow(move |scope| {
                let (input, stream) = scope.new_input::<String>();
                (input, stream.probe())
            });

            // introduce data and watch!
            for round in 0..10 {
                assert!(!probe.done());
                assert!(probe.less_equal(&RootTimestamp::new(round)));
                assert!(!probe.less_than(&RootTimestamp::new(round)));
                assert!(probe.less_than(&RootTimestamp::new(round + 1)));
                input.advance_to(round + 1);
                worker.step();
            }

            // seal the input
            input.close();

            // finish off any remaining work
            worker.step();
            assert!(probe.done());
        }).unwrap();
    }

}
<commit_msg>added with_frontier<commit_after>//! Monitor progress at a `Stream`.

use std::rc::Rc;
use std::cell::RefCell;

use progress::{Timestamp, Operate, Antichain};
use progress::frontier::MutableAntichain;
use progress::nested::subgraph::{Source, Target};
use progress::count_map::CountMap;

use dataflow::channels::pushers::Tee;
use dataflow::channels::pushers::Counter as PushCounter;
use dataflow::channels::pushers::buffer::Buffer as PushBuffer;
use dataflow::channels::pact::{ParallelizationContract, Pipeline};
use dataflow::channels::pullers::Counter as PullCounter;

use Data;
use dataflow::{Stream, Scope};

/// Monitors progress at a `Stream`.
pub trait Probe<G: Scope, D: Data> {
    /// Constructs a progress probe which indicates which timestamps have elapsed at the operator.
    ///
    /// #Examples
    /// ```
    /// use timely::*;
    /// use timely::dataflow::Scope;
    /// use timely::dataflow::operators::{Input, Probe, Inspect};
    /// use timely::progress::timestamp::RootTimestamp;
    ///
    /// // construct and execute a timely dataflow
    /// timely::execute(Configuration::Thread, |worker| {
    ///
    ///     // add an input and base computation off of it
    ///     let (mut input, probe) = worker.dataflow(|scope| {
    ///         let (input, stream) = scope.new_input();
    ///         let probe = stream.inspect(|x| println!("hello {:?}", x))
    ///                           .probe();
    ///         (input, probe)
    ///     });
    ///
    ///     // introduce input, advance computation
    ///     for round in 0..10 {
    ///         input.send(round);
    ///         input.advance_to(round + 1);
    ///         worker.step_while(|| probe.less_than(input.time()));
    ///     }
    /// }).unwrap();
    /// ```
    fn probe(&self) -> Handle<G::Timestamp>;

    /// Inserts a progress probe in a stream.
    ///
    /// #Examples
    /// ```
    /// use timely::*;
    /// use timely::dataflow::Scope;
    /// use timely::dataflow::operators::{Input, Probe, Inspect};
    /// use timely::dataflow::operators::probe::Handle;
    /// use timely::progress::timestamp::RootTimestamp;
    ///
    /// // construct and execute a timely dataflow
    /// timely::execute(Configuration::Thread, |worker| {
    ///
    ///     // add an input and base computation off of it
    ///     let mut probe = Handle::new();
    ///     let mut input = worker.dataflow(|scope| {
    ///         let (input, stream) = scope.new_input();
    ///         stream.probe_with(&mut probe)
    ///               .inspect(|x| println!("hello {:?}", x));
    ///
    ///         input
    ///     });
    ///
    ///     // introduce input, advance computation
    ///     for round in 0..10 {
    ///         input.send(round);
    ///         input.advance_to(round + 1);
    ///         worker.step_while(|| probe.less_than(input.time()));
    ///     }
    /// }).unwrap();
    /// ```
    fn probe_with(&self, handle: &mut Handle<G::Timestamp>) -> Stream<G, D>;
}

impl<G: Scope, D: Data> Probe<G, D> for Stream<G, D> {
    fn probe(&self) -> Handle<G::Timestamp> {

        // the frontier is shared state; scope updates, handle reads.
        let handle = Handle::new();

        let mut scope = self.scope();   // clones the scope
        let channel_id = scope.new_identifier();

        let (sender, receiver) = Pipeline.connect(&mut scope, channel_id);
        let (targets, registrar) = Tee::<G::Timestamp,D>::new();
        let operator = Operator {
            input: PullCounter::new(receiver),
            output: PushBuffer::new(PushCounter::new(targets, Rc::new(RefCell::new(CountMap::new())))),
            frontier: handle.frontier.clone(),
        };

        let index = scope.add_operator(operator);
        self.connect_to(Target { index: index, port: 0 }, sender, channel_id);
        Stream::new(Source { index: index, port: 0 }, registrar, scope);
        handle
    }
    fn probe_with(&self, handle: &mut Handle<G::Timestamp>) -> Stream<G, D> {

        let mut scope = self.scope();   // clones the scope
        let channel_id = scope.new_identifier();

        let (sender, receiver) = Pipeline.connect(&mut scope, channel_id);
        let (targets, registrar) = Tee::<G::Timestamp,D>::new();
        let operator = Operator {
            input: PullCounter::new(receiver),
            output: PushBuffer::new(PushCounter::new(targets, Rc::new(RefCell::new(CountMap::new())))),
            frontier: handle.frontier.clone(),
        };

        let index = scope.add_operator(operator);
        self.connect_to(Target { index: index, port: 0 }, sender, channel_id);
        Stream::new(Source { index: index, port: 0 }, registrar, scope)
    }
}

/// Reports information about progress at the probe.
pub struct Handle<T:Timestamp> {
    frontier: Rc<RefCell<MutableAntichain<T>>>
}

impl<T: Timestamp> Handle<T> {
    /// returns true iff the frontier is strictly less than `time`.
    #[inline] pub fn less_than(&self, time: &T) -> bool { self.frontier.borrow().less_than(time) }
    /// returns true iff the frontier is less than or equal to `time`.
    #[inline] pub fn less_equal(&self, time: &T) -> bool { self.frontier.borrow().less_equal(time) }
    /// returns true iff the frontier is empty.
    #[inline] pub fn done(&self) -> bool { self.frontier.borrow().elements().len() == 0 }
    /// Allocates a new handle.
    #[inline] pub fn new() -> Self { Handle { frontier: Rc::new(RefCell::new(MutableAntichain::new())) } }

    /// Invokes a method on the frontier, returning its result.
    ///
    /// This method allows inspection of the frontier, which cannot be returned by reference as
    /// it is on the other side of a `RefCell`.
    ///
    /// #Examples
    ///
    /// ```
    /// use timely::dataflow::operators::probe::Handle;
    ///
    /// let handle = Handle::<usize>::new();
    /// let frontier = handle.with_frontier(|frontier| frontier.to_vec());
    /// ```
    #[inline]
    pub fn with_frontier<R, F: Fn(&[T])->R>(&self, function: F) -> R {
        function(self.frontier.borrow().elements())
    }
}

impl<T: Timestamp> Clone for Handle<T> {
    fn clone(&self) -> Self {
        Handle {
            frontier: self.frontier.clone()
        }
    }
}

struct Operator<T:Timestamp, D: Data> {
    input: PullCounter<T, D>,
    output: PushBuffer<T, D, PushCounter<T, D, Tee<T, D>>>,
    frontier: Rc<RefCell<MutableAntichain<T>>>
}

impl<T:Timestamp, D: Data> Operate<T> for Operator<T, D> {
    fn name(&self) -> String { "Probe".to_owned() }
    fn inputs(&self) -> usize { 1 }
    fn outputs(&self) -> usize { 1 }

    // we need to set the initial value of the frontier
    fn set_external_summary(&mut self, _: Vec<Vec<Antichain<T::Summary>>>, counts: &mut [CountMap<T>]) {
        let mut borrow = self.frontier.borrow_mut();
        while let Some((time, delta)) = counts[0].pop() {
            borrow.update(&time, delta);
        }
    }

    // each change to the frontier should be shared
    fn push_external_progress(&mut self, counts: &mut [CountMap<T>]) {
        let mut borrow = self.frontier.borrow_mut();
        while let Some((time, delta)) = counts[0].pop() {
            borrow.update(&time, delta);
        }
    }

    // the scope does nothing. this is actually a problem, because "reachability" assumes all messages on each edge.
    fn pull_internal_progress(&mut self, consumed: &mut [CountMap<T>], _: &mut [CountMap<T>], produced: &mut [CountMap<T>]) -> bool {

        while let Some((time, data)) = self.input.next() {
            self.output.session(time).give_content(data);
        }

        self.output.cease();

        // extract what we know about progress from the input and output adapters.
        self.input.pull_progress(&mut consumed[0]);
        self.output.inner().pull_progress(&mut produced[0]);

        false
    }
}

#[cfg(test)]
mod tests {

    use ::Configuration;
    use ::progress::timestamp::RootTimestamp;
    use dataflow::operators::{Input, Probe};

    #[test]
    fn probe() {

        // initializes and runs a timely dataflow computation
        ::execute(Configuration::Thread, |worker| {

            // create a new input, and inspect its output
            let (mut input, probe) = worker.dataflow(move |scope| {
                let (input, stream) = scope.new_input::<String>();
                (input, stream.probe())
            });

            // introduce data and watch!
            for round in 0..10 {
                assert!(!probe.done());
                assert!(probe.less_equal(&RootTimestamp::new(round)));
                assert!(!probe.less_than(&RootTimestamp::new(round)));
                assert!(probe.less_than(&RootTimestamp::new(round + 1)));
                input.advance_to(round + 1);
                worker.step();
            }

            // seal the input
            input.close();

            // finish off any remaining work
            worker.step();
            assert!(probe.done());
        }).unwrap();
    }

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

//! udev-related methods
use std::{ffi::OsStr, fmt};

use crate::{
    engine::types::{DevicePath, UdevEngineDevice},
    stratis::{StratisError, StratisResult},
};

/// Key for a udev property
pub const FS_TYPE_KEY: &str = "ID_FS_TYPE";

/// Possible values for the "ID_FS_TYPE" udev property
pub const STRATIS_FS_TYPE: &str = "stratis";
pub const CRYPTO_FS_TYPE: &str = "crypto_LUKS";

/// Possible value for a udev subsystem designation
pub const SUBSYSTEM_BLOCK: &str = "block";

/// Make an enumerator for enumerating block devices. Return an error if there
/// was any udev-related error.
pub fn block_enumerator(context: &libudev::Context) -> libudev::Result<libudev::Enumerator<'_>> {
    let mut enumerator = libudev::Enumerator::new(context)?;
    enumerator.match_subsystem(SUBSYSTEM_BLOCK)?;
    Ok(enumerator)
}

/// Get a udev property with the given name for the given device.
/// Returns None if no udev property found for the given property name.
/// Returns an error if the value of the property can not be converted to
/// a string using the standard conversion for this OS.
pub fn get_udev_property<T: AsRef<OsStr>>(
    device: &UdevEngineDevice,
    property_name: T,
) -> Option<StratisResult<String>>
where
    T: fmt::Display,
{
    device
        .property_value(&property_name)
        .map(|opt| opt.map(|s| s.to_string()))
}

/// Returns true if udev indicates that the device is a multipath member
/// device, else false. Returns an error on a failure to interpret the
/// value.
fn is_multipath_member(device: &UdevEngineDevice) -> StratisResult<bool> {
    match get_udev_property(device, "DM_MULTIPATH_DEVICE_PATH") {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == "1"),
        Some(Err(err)) => Err(err),
    }
}

/// If the expression is true, then it seems that no other system is
/// known to udev to claim this device.
fn is_unclaimed(device: &UdevEngineDevice) -> bool {
    (get_udev_property(device, "ID_PART_TABLE_TYPE").is_none()
        || get_udev_property(device, "ID_PART_ENTRY_DISK").is_some())
        && get_udev_property(device, "ID_FS_USAGE").is_none()
}

/// Return true if the device is identified by udev as belonging to Stratis.
/// Return an error if a udev property value could not be converted.
fn is_stratis(device: &UdevEngineDevice) -> StratisResult<bool> {
    match get_udev_property(device, FS_TYPE_KEY) {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == STRATIS_FS_TYPE),
        Some(Err(err)) => Err(err),
    }
}

/// Return true if the device is identified by udev as being an encrypted
/// LUKS device. Return an error if a udev property could not be converted.
fn is_luks(device: &UdevEngineDevice) -> StratisResult<bool> {
    match get_udev_property(device, FS_TYPE_KEY) {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == CRYPTO_FS_TYPE),
        Some(Err(err)) => Err(err),
    }
}

/// An enum to encode udev classification of a device
#[derive(Debug, Eq, PartialEq)]
pub enum UdevOwnership {
    Luks,
    MultipathMember,
    Stratis,
    Theirs,
    Unowned,
}

impl fmt::Display for UdevOwnership {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            UdevOwnership::Luks => write!(f, "LUKS encrypted block device"),
            UdevOwnership::MultipathMember => write!(f, "member of a multipath block device"),
            UdevOwnership::Stratis => write!(f, "Stratis block device"),
            UdevOwnership::Theirs => write!(f, "block device which appears to be owned"),
            UdevOwnership::Unowned => write!(f, "block device which appears to be unowned"),
        }
    }
}

/// Decide the ownership of a device based on udev information.
///
/// Always check multipath member status first. The theory is that a multipath
/// member may also appear to belong to Stratis, but it should not be identified
/// as a Stratis device.
///
/// Note that the designation Theirs, i.e., the device already belongs to some
/// other entity is the default designation. This seems counterintuitive, but
/// it is the unclaimed designation that has a boolean expression on udev
/// properties associated with it.
pub fn decide_ownership(device: &UdevEngineDevice) -> StratisResult<UdevOwnership> {
    || -> StratisResult<UdevOwnership> {
        // We believe that it is possible to be a multipath member and also to
        // be identified as a Stratis device. The designations are not mutually
        // exclusive, but the multipath member device must not be used by Stratis.
        if is_multipath_member(device)? {
            return Ok(UdevOwnership::MultipathMember);
        }

        // We believe that the following designations are mutually exclusive, i.e.
        // it is not possible to be a Stratis device and also to appear unowned.
        Ok(if is_stratis(device)? {
            UdevOwnership::Stratis
        } else if is_luks(device)? {
            UdevOwnership::Luks
        } else if is_unclaimed(device) {
            UdevOwnership::Unowned
        } else {
            UdevOwnership::Theirs
        })
    }()
    .map_err(|err| {
        StratisError::Chained(
            "Could not determine ownership of a device from a udev database entry".to_string(),
            Box::new(err),
        )
    })
}

/// Locate a udev block device with the specified devnode and apply a function
/// to that device, returning the result.
/// Note that this does require iterating through the blockdevs in the udev
/// database, so it is essentially linear in the number of block devices.
/// This approach was initially required because udev lifetimes did not allow
/// the object representing the device to be returned from a method. This is
/// now unnecessary; the method could simply return a UdevEngineDevice.
/// It was decided not to take that step as, at some point in the future, it
/// might be reasonable to revert to an approach which reads the udev device
/// object and applies a function only to selected properties or attributes.
pub fn block_device_apply<F, U>(device_path: &DevicePath, f: F) -> StratisResult<Option<U>>
where
    F: FnOnce(&UdevEngineDevice) -> U,
{
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    Ok(enumerator
        .scan_devices()?
        .filter(|dev| dev.is_initialized())
        .find(|x| x.devnode().map_or(false, |d| **device_path == *d))
        .map(|ref d| f(&UdevEngineDevice::from(d))))
}
<commit_msg>Resolve type_repetition_in_bounds lint<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/.

//! udev-related methods
use std::{ffi::OsStr, fmt};

use crate::{
    engine::types::{DevicePath, UdevEngineDevice},
    stratis::{StratisError, StratisResult},
};

/// Key for a udev property
pub const FS_TYPE_KEY: &str = "ID_FS_TYPE";

/// Possible values for the "ID_FS_TYPE" udev property
pub const STRATIS_FS_TYPE: &str = "stratis";
pub const CRYPTO_FS_TYPE: &str = "crypto_LUKS";

/// Possible value for a udev subsystem designation
pub const SUBSYSTEM_BLOCK: &str = "block";

/// Make an enumerator for enumerating block devices. Return an error if there
/// was any udev-related error.
pub fn block_enumerator(context: &libudev::Context) -> libudev::Result<libudev::Enumerator<'_>> {
    let mut enumerator = libudev::Enumerator::new(context)?;
    enumerator.match_subsystem(SUBSYSTEM_BLOCK)?;
    Ok(enumerator)
}

/// Get a udev property with the given name for the given device.
/// Returns None if no udev property found for the given property name.
/// Returns an error if the value of the property can not be converted to
/// a string using the standard conversion for this OS.
pub fn get_udev_property<T: AsRef<OsStr> + fmt::Display>(
    device: &UdevEngineDevice,
    property_name: T,
) -> Option<StratisResult<String>> {
    device
        .property_value(&property_name)
        .map(|opt| opt.map(|s| s.to_string()))
}

/// Returns true if udev indicates that the device is a multipath member
/// device, else false. Returns an error on a failure to interpret the
/// value.
fn is_multipath_member(device: &UdevEngineDevice) -> StratisResult<bool> {
    match get_udev_property(device, "DM_MULTIPATH_DEVICE_PATH") {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == "1"),
        Some(Err(err)) => Err(err),
    }
}

/// If the expression is true, then it seems that no other system is
/// known to udev to claim this device.
fn is_unclaimed(device: &UdevEngineDevice) -> bool {
    (get_udev_property(device, "ID_PART_TABLE_TYPE").is_none()
        || get_udev_property(device, "ID_PART_ENTRY_DISK").is_some())
        && get_udev_property(device, "ID_FS_USAGE").is_none()
}

/// Return true if the device is identified by udev as belonging to Stratis.
/// Return an error if a udev property value could not be converted.
fn is_stratis(device: &UdevEngineDevice) -> StratisResult<bool> {
    match get_udev_property(device, FS_TYPE_KEY) {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == STRATIS_FS_TYPE),
        Some(Err(err)) => Err(err),
    }
}

/// Return true if the device is identified by udev as being an encrypted
/// LUKS device. Return an error if a udev property could not be converted.
fn is_luks(device: &UdevEngineDevice) -> StratisResult<bool> {
    match get_udev_property(device, FS_TYPE_KEY) {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == CRYPTO_FS_TYPE),
        Some(Err(err)) => Err(err),
    }
}

/// An enum to encode udev classification of a device
#[derive(Debug, Eq, PartialEq)]
pub enum UdevOwnership {
    Luks,
    MultipathMember,
    Stratis,
    Theirs,
    Unowned,
}

impl fmt::Display for UdevOwnership {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            UdevOwnership::Luks => write!(f, "LUKS encrypted block device"),
            UdevOwnership::MultipathMember => write!(f, "member of a multipath block device"),
            UdevOwnership::Stratis => write!(f, "Stratis block device"),
            UdevOwnership::Theirs => write!(f, "block device which appears to be owned"),
            UdevOwnership::Unowned => write!(f, "block device which appears to be unowned"),
        }
    }
}

/// Decide the ownership of a device based on udev information.
///
/// Always check multipath member status first. The theory is that a multipath
/// member may also appear to belong to Stratis, but it should not be identified
/// as a Stratis device.
///
/// Note that the designation Theirs, i.e., the device already belongs to some
/// other entity is the default designation. This seems counterintuitive, but
/// it is the unclaimed designation that has a boolean expression on udev
/// properties associated with it.
pub fn decide_ownership(device: &UdevEngineDevice) -> StratisResult<UdevOwnership> {
    || -> StratisResult<UdevOwnership> {
        // We believe that it is possible to be a multipath member and also to
        // be identified as a Stratis device. The designations are not mutually
        // exclusive, but the multipath member device must not be used by Stratis.
        if is_multipath_member(device)? {
            return Ok(UdevOwnership::MultipathMember);
        }

        // We believe that the following designations are mutually exclusive, i.e.
        // it is not possible to be a Stratis device and also to appear unowned.
        Ok(if is_stratis(device)? {
            UdevOwnership::Stratis
        } else if is_luks(device)? {
            UdevOwnership::Luks
        } else if is_unclaimed(device) {
            UdevOwnership::Unowned
        } else {
            UdevOwnership::Theirs
        })
    }()
    .map_err(|err| {
        StratisError::Chained(
            "Could not determine ownership of a device from a udev database entry".to_string(),
            Box::new(err),
        )
    })
}

/// Locate a udev block device with the specified devnode and apply a function
/// to that device, returning the result.
/// Note that this does require iterating through the blockdevs in the udev
/// database, so it is essentially linear in the number of block devices.
/// This approach was initially required because udev lifetimes did not allow
/// the object representing the device to be returned from a method. This is
/// now unnecessary; the method could simply return a UdevEngineDevice.
/// It was decided not to take that step as, at some point in the future, it
/// might be reasonable to revert to an approach which reads the udev device
/// object and applies a function only to selected properties or attributes.
pub fn block_device_apply<F, U>(device_path: &DevicePath, f: F) -> StratisResult<Option<U>>
where
    F: FnOnce(&UdevEngineDevice) -> U,
{
    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    Ok(enumerator
        .scan_devices()?
        .filter(|dev| dev.is_initialized())
        .find(|x| x.devnode().map_or(false, |d| **device_path == *d))
        .map(|ref d| f(&UdevEngineDevice::from(d))))
}
<|endoftext|>


<commit_before>use ::libc;
use ::cssparser::{Parser, ParserInput, Token, BasicParseError};
use ::glib::translate::*;
use ::glib_sys;

use std::f64::consts::*;
use std::mem;
use std::ptr;
use std::slice;
use std::str;

#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
    pub display: String
}

impl ParseError {
    pub fn new<T: AsRef<str>> (msg: T) -> ParseError {
        ParseError { display: msg.as_ref ().to_string () }
    }
}

impl<'a> From<BasicParseError<'a>> for ParseError {
    fn from (_: BasicParseError) -> ParseError {
        ParseError::new ("parse error")
    }
}

pub trait Parse: Sized {
    type Data;
    type Err;

    fn parse (s: &str, data: Self::Data) -> Result<Self, Self::Err>;
}

// angle:
// https://www.w3.org/TR/SVG/types.html#DataTypeAngle
//
// angle ::= number ("deg" | "grad" | "rad")?
//
// Returns an f64 angle in degrees

pub fn angle_degrees (s: &str) -> Result <f64, ParseError> {
    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let angle = {
        let token = parser.next ()
            .map_err (|_| ParseError::new ("expected angle"))?;

        match *token {
            Token::Number { value, .. } => f64::from(value),

            Token::Dimension { value, ref unit, .. } => {
                let value = f64::from(value);

                match unit.as_ref () {
                    "deg"  => value,
                    "grad" => value * 360.0 / 400.0,
                    "rad"  => value * 180.0 / PI,
                    _      => return Err (ParseError::new ("expected 'deg' | 'grad' | 'rad'"))
                }
            },

            _ => return Err (ParseError::new ("expected angle"))
        }
    };

    parser.expect_exhausted ().map_err (|_| ParseError::new ("expected angle"))?;

    Ok (angle)
}

pub fn optional_comma (parser: &mut Parser) {
    let _ = parser.try (|p| p.expect_comma ());
}

// number-optional-number
//
// https://www.w3.org/TR/SVG/types.html#DataTypeNumberOptionalNumber

pub fn number_optional_number (s: &str) -> Result <(f64, f64), ParseError> {
    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let x = f64::from(parser.expect_number ()?);

    if !parser.is_exhausted () {
        let state = parser.state ();

        match *parser.next ()? {
            Token::Comma => {},
            _ => parser.reset (&state)
        };

        let y = f64::from(parser.expect_number ()?);

        parser.expect_exhausted ()?;

        Ok ((x, y))
    } else {
        Ok ((x, x))
    }
}

#[no_mangle]
pub extern fn rsvg_css_parse_number_optional_number (s: *const libc::c_char,
                                                     out_x: *mut f64,
                                                     out_y: *mut f64) -> glib_sys::gboolean {
    assert! (!s.is_null ());
    assert! (!out_x.is_null ());
    assert! (!out_y.is_null ());

    let string = unsafe { String::from_glib_none (s) };

    match number_optional_number (&string) {
        Ok ((x, y)) => {
            unsafe {
                *out_x = x;
                *out_y = y;
            }
            true
        },

        Err (_) => {
            unsafe {
                *out_x = 0.0;
                *out_y = 0.0;
            }
            false
        }
    }.to_glib ()
}

// Parse a list-of-points as for polyline and polygon elements
// https://www.w3.org/TR/SVG/shapes.html#PointsBNF

pub fn list_of_points (string: &str) -> Result <Vec<(f64, f64)>, ParseError> {
    let mut input = ParserInput::new (string);
    let mut parser = Parser::new (&mut input);

    let mut v = Vec::new ();

    loop {
        let x = f64::from(parser.expect_number ()?);

        optional_comma (&mut parser);

        let y = f64::from(parser.expect_number ()?);

        v.push ((x, y));

        if parser.is_exhausted () {
            break;
        }

        match parser.next_including_whitespace () {
            Ok (&Token::WhiteSpace(_)) => (),
            _ => optional_comma (&mut parser)
        }
    }

    Ok (v)
}

// Lists of number values

pub enum ListLength {
    Exact (usize),
    Maximum (usize)
}

#[derive(Debug, PartialEq)]
pub enum NumberListError {
    IncorrectNumberOfElements,
    Parse (ParseError)
}

pub fn number_list (s: &str, length: ListLength) -> Result <Vec<f64>, NumberListError> {
    let n;

    match length {
        ListLength::Exact (l)   => { assert! (l > 0); n = l; }
        ListLength::Maximum (l) => { assert! (l > 0); n = l; }
    }

    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let mut v = Vec::<f64>::with_capacity (n);

    for i in 0..n {
        v.push (f64::from(parser.expect_number ().map_err (|_| NumberListError::Parse (ParseError::new ("expected number")))?));

        if i != n - 1 {
            optional_comma (&mut parser);
        }

        if parser.is_exhausted () {
            if let ListLength::Maximum (_) = length {
                break;
            }
        }
    }

    parser.expect_exhausted ().map_err (|_| NumberListError::IncorrectNumberOfElements)?;

    Ok(v)
}

#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum NumberListLength {
    Exact,
    Maximum
}

#[no_mangle]
pub extern fn rsvg_css_parse_number_list (in_str:   *const libc::c_char,
                                          nlength:  NumberListLength,
                                          size:     libc::size_t,
                                          out_list: *mut *const libc::c_double,
                                          out_list_length: *mut libc::size_t) -> glib_sys::gboolean {
    assert! (!in_str.is_null ());
    assert! (!out_list.is_null ());
    assert! (!out_list_length.is_null ());

    let length = match nlength {
        NumberListLength::Exact   => ListLength::Exact (size),
        NumberListLength::Maximum => ListLength::Maximum (size)
    };

    let s = unsafe { String::from_glib_none (in_str) };

    let result = number_list (&s, length);

    match result {
        Ok (number_list) => {
            let num_elems = number_list.len ();

            let c_array = unsafe {
                glib_sys::g_malloc_n (num_elems,
                                      mem::size_of::<libc::c_double> ())
                    as *mut libc::c_double
            };

            let array = unsafe { slice::from_raw_parts_mut (c_array, num_elems) };

            array.copy_from_slice (&number_list);

            unsafe {
                *out_list = c_array;
                *out_list_length = num_elems;
            }

            true
        },

        Err (_) => {
            unsafe {
                *out_list = ptr::null ();
                *out_list_length = 0;
            }
            false
        }
    }.to_glib ()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_number_optional_number () {
        assert_eq! (number_optional_number ("1, 2"), Ok ((1.0, 2.0)));
        assert_eq! (number_optional_number ("1 2"),  Ok ((1.0, 2.0)));
        assert_eq! (number_optional_number ("1"),    Ok ((1.0, 1.0)));

        assert_eq! (number_optional_number ("-1, -2"), Ok ((-1.0, -2.0)));
        assert_eq! (number_optional_number ("-1 -2"),  Ok ((-1.0, -2.0)));
        assert_eq! (number_optional_number ("-1"),     Ok ((-1.0, -1.0)));
    }

    #[test]
    fn invalid_number_optional_number () {
        assert! (number_optional_number ("").is_err ());
        assert! (number_optional_number ("1x").is_err ());
        assert! (number_optional_number ("x1").is_err ());
        assert! (number_optional_number ("1 x").is_err ());
        assert! (number_optional_number ("1 , x").is_err ());
        assert! (number_optional_number ("1 , 2x").is_err ());
        assert! (number_optional_number ("1 2 x").is_err ());
    }

    #[test]
    fn parses_list_of_points () {
        assert_eq! (list_of_points (" 1 2 "),      Ok (vec! [(1.0, 2.0)]));
        assert_eq! (list_of_points ("1 2 3 4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2,3,4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2 3,4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2 -3,4"), Ok (vec! [(1.0, 2.0), (-3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2,-3,4"), Ok (vec! [(1.0, 2.0), (-3.0, 4.0)]));
    }

    #[test]
    fn errors_on_invalid_list_of_points () {
        assert! (list_of_points ("-1-2-3-4").is_err ());
        assert! (list_of_points ("1 2-3,-4").is_err ());
    }

    #[test]
    fn parses_angle () {
        assert_eq! (angle_degrees ("0"),        Ok (0.0));
        assert_eq! (angle_degrees ("15"),       Ok (15.0));
        assert_eq! (angle_degrees ("180.5deg"), Ok (180.5));
        assert_eq! (angle_degrees ("1rad"),     Ok (180.0 / PI));
        assert_eq! (angle_degrees ("-400grad"), Ok (-360.0));

        assert! (angle_degrees ("").is_err ());
        assert! (angle_degrees ("foo").is_err ());
        assert! (angle_degrees ("300foo").is_err ());
    }

    #[test]
    fn parses_number_list () {
        assert_eq! (number_list ("5", ListLength::Exact (1)),
                    Ok (vec! [5.0]));

        assert_eq! (number_list ("1 2 3 4", ListLength::Exact (4)),
                    Ok (vec! [1.0, 2.0, 3.0, 4.0]));

        assert_eq! (number_list ("5", ListLength::Maximum (1)),
                    Ok (vec! [5.0]));

        assert_eq! (number_list ("1.0, -2.5", ListLength::Maximum (2)),
                    Ok (vec! [1.0, -2.5]));

        assert_eq! (number_list ("5 6", ListLength::Maximum (3)),
                    Ok (vec! [5.0, 6.0]));
    }

    #[test]
    fn errors_on_invalid_number_list () {
        // empty
        assert! (number_list ("", ListLength::Exact (1)).is_err ());

        // garbage
        assert! (number_list ("foo", ListLength::Exact (1)).is_err ());
        assert! (number_list ("1foo", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 foo", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 foo 2", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1,foo", ListLength::Exact (2)).is_err ());

        // too many
        assert! (number_list ("1 2", ListLength::Exact (1)).is_err ());
        assert! (number_list ("1,2,3", ListLength::Maximum (2)).is_err ());

        // extra token
        assert! (number_list ("1,", ListLength::Exact (1)).is_err ());

        // too few
        assert! (number_list ("1", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 2", ListLength::Exact (3)).is_err ());
    }
}
<commit_msg>rsvg_css_parse_number_optional_number(): Don't copy the incoming string<commit_after>use ::libc;
use ::cssparser::{Parser, ParserInput, Token, BasicParseError};
use ::glib::translate::*;
use ::glib_sys;

use std::f64::consts::*;
use std::mem;
use std::ptr;
use std::slice;
use std::str;

use util::utf8_cstr;

#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
    pub display: String
}

impl ParseError {
    pub fn new<T: AsRef<str>> (msg: T) -> ParseError {
        ParseError { display: msg.as_ref ().to_string () }
    }
}

impl<'a> From<BasicParseError<'a>> for ParseError {
    fn from (_: BasicParseError) -> ParseError {
        ParseError::new ("parse error")
    }
}

pub trait Parse: Sized {
    type Data;
    type Err;

    fn parse (s: &str, data: Self::Data) -> Result<Self, Self::Err>;
}

// angle:
// https://www.w3.org/TR/SVG/types.html#DataTypeAngle
//
// angle ::= number ("deg" | "grad" | "rad")?
//
// Returns an f64 angle in degrees

pub fn angle_degrees (s: &str) -> Result <f64, ParseError> {
    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let angle = {
        let token = parser.next ()
            .map_err (|_| ParseError::new ("expected angle"))?;

        match *token {
            Token::Number { value, .. } => f64::from(value),

            Token::Dimension { value, ref unit, .. } => {
                let value = f64::from(value);

                match unit.as_ref () {
                    "deg"  => value,
                    "grad" => value * 360.0 / 400.0,
                    "rad"  => value * 180.0 / PI,
                    _      => return Err (ParseError::new ("expected 'deg' | 'grad' | 'rad'"))
                }
            },

            _ => return Err (ParseError::new ("expected angle"))
        }
    };

    parser.expect_exhausted ().map_err (|_| ParseError::new ("expected angle"))?;

    Ok (angle)
}

pub fn optional_comma (parser: &mut Parser) {
    let _ = parser.try (|p| p.expect_comma ());
}

// number-optional-number
//
// https://www.w3.org/TR/SVG/types.html#DataTypeNumberOptionalNumber

pub fn number_optional_number (s: &str) -> Result <(f64, f64), ParseError> {
    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let x = f64::from(parser.expect_number ()?);

    if !parser.is_exhausted () {
        let state = parser.state ();

        match *parser.next ()? {
            Token::Comma => {},
            _ => parser.reset (&state)
        };

        let y = f64::from(parser.expect_number ()?);

        parser.expect_exhausted ()?;

        Ok ((x, y))
    } else {
        Ok ((x, x))
    }
}

#[no_mangle]
pub extern fn rsvg_css_parse_number_optional_number (s: *const libc::c_char,
                                                     out_x: *mut f64,
                                                     out_y: *mut f64) -> glib_sys::gboolean {
    assert! (!s.is_null ());
    assert! (!out_x.is_null ());
    assert! (!out_y.is_null ());

    let string = unsafe { utf8_cstr(s) };

    match number_optional_number (string) {
        Ok ((x, y)) => {
            unsafe {
                *out_x = x;
                *out_y = y;
            }
            true
        },

        Err (_) => {
            unsafe {
                *out_x = 0.0;
                *out_y = 0.0;
            }
            false
        }
    }.to_glib ()
}

// Parse a list-of-points as for polyline and polygon elements
// https://www.w3.org/TR/SVG/shapes.html#PointsBNF

pub fn list_of_points (string: &str) -> Result <Vec<(f64, f64)>, ParseError> {
    let mut input = ParserInput::new (string);
    let mut parser = Parser::new (&mut input);

    let mut v = Vec::new ();

    loop {
        let x = f64::from(parser.expect_number ()?);

        optional_comma (&mut parser);

        let y = f64::from(parser.expect_number ()?);

        v.push ((x, y));

        if parser.is_exhausted () {
            break;
        }

        match parser.next_including_whitespace () {
            Ok (&Token::WhiteSpace(_)) => (),
            _ => optional_comma (&mut parser)
        }
    }

    Ok (v)
}

// Lists of number values

pub enum ListLength {
    Exact (usize),
    Maximum (usize)
}

#[derive(Debug, PartialEq)]
pub enum NumberListError {
    IncorrectNumberOfElements,
    Parse (ParseError)
}

pub fn number_list (s: &str, length: ListLength) -> Result <Vec<f64>, NumberListError> {
    let n;

    match length {
        ListLength::Exact (l)   => { assert! (l > 0); n = l; }
        ListLength::Maximum (l) => { assert! (l > 0); n = l; }
    }

    let mut input = ParserInput::new (s);
    let mut parser = Parser::new (&mut input);

    let mut v = Vec::<f64>::with_capacity (n);

    for i in 0..n {
        v.push (f64::from(parser.expect_number ().map_err (|_| NumberListError::Parse (ParseError::new ("expected number")))?));

        if i != n - 1 {
            optional_comma (&mut parser);
        }

        if parser.is_exhausted () {
            if let ListLength::Maximum (_) = length {
                break;
            }
        }
    }

    parser.expect_exhausted ().map_err (|_| NumberListError::IncorrectNumberOfElements)?;

    Ok(v)
}

#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum NumberListLength {
    Exact,
    Maximum
}

#[no_mangle]
pub extern fn rsvg_css_parse_number_list (in_str:   *const libc::c_char,
                                          nlength:  NumberListLength,
                                          size:     libc::size_t,
                                          out_list: *mut *const libc::c_double,
                                          out_list_length: *mut libc::size_t) -> glib_sys::gboolean {
    assert! (!in_str.is_null ());
    assert! (!out_list.is_null ());
    assert! (!out_list_length.is_null ());

    let length = match nlength {
        NumberListLength::Exact   => ListLength::Exact (size),
        NumberListLength::Maximum => ListLength::Maximum (size)
    };

    let s = unsafe { String::from_glib_none (in_str) };

    let result = number_list (&s, length);

    match result {
        Ok (number_list) => {
            let num_elems = number_list.len ();

            let c_array = unsafe {
                glib_sys::g_malloc_n (num_elems,
                                      mem::size_of::<libc::c_double> ())
                    as *mut libc::c_double
            };

            let array = unsafe { slice::from_raw_parts_mut (c_array, num_elems) };

            array.copy_from_slice (&number_list);

            unsafe {
                *out_list = c_array;
                *out_list_length = num_elems;
            }

            true
        },

        Err (_) => {
            unsafe {
                *out_list = ptr::null ();
                *out_list_length = 0;
            }
            false
        }
    }.to_glib ()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_number_optional_number () {
        assert_eq! (number_optional_number ("1, 2"), Ok ((1.0, 2.0)));
        assert_eq! (number_optional_number ("1 2"),  Ok ((1.0, 2.0)));
        assert_eq! (number_optional_number ("1"),    Ok ((1.0, 1.0)));

        assert_eq! (number_optional_number ("-1, -2"), Ok ((-1.0, -2.0)));
        assert_eq! (number_optional_number ("-1 -2"),  Ok ((-1.0, -2.0)));
        assert_eq! (number_optional_number ("-1"),     Ok ((-1.0, -1.0)));
    }

    #[test]
    fn invalid_number_optional_number () {
        assert! (number_optional_number ("").is_err ());
        assert! (number_optional_number ("1x").is_err ());
        assert! (number_optional_number ("x1").is_err ());
        assert! (number_optional_number ("1 x").is_err ());
        assert! (number_optional_number ("1 , x").is_err ());
        assert! (number_optional_number ("1 , 2x").is_err ());
        assert! (number_optional_number ("1 2 x").is_err ());
    }

    #[test]
    fn parses_list_of_points () {
        assert_eq! (list_of_points (" 1 2 "),      Ok (vec! [(1.0, 2.0)]));
        assert_eq! (list_of_points ("1 2 3 4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2,3,4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2 3,4"),  Ok (vec! [(1.0, 2.0), (3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2 -3,4"), Ok (vec! [(1.0, 2.0), (-3.0, 4.0)]));
        assert_eq! (list_of_points ("1,2,-3,4"), Ok (vec! [(1.0, 2.0), (-3.0, 4.0)]));
    }

    #[test]
    fn errors_on_invalid_list_of_points () {
        assert! (list_of_points ("-1-2-3-4").is_err ());
        assert! (list_of_points ("1 2-3,-4").is_err ());
    }

    #[test]
    fn parses_angle () {
        assert_eq! (angle_degrees ("0"),        Ok (0.0));
        assert_eq! (angle_degrees ("15"),       Ok (15.0));
        assert_eq! (angle_degrees ("180.5deg"), Ok (180.5));
        assert_eq! (angle_degrees ("1rad"),     Ok (180.0 / PI));
        assert_eq! (angle_degrees ("-400grad"), Ok (-360.0));

        assert! (angle_degrees ("").is_err ());
        assert! (angle_degrees ("foo").is_err ());
        assert! (angle_degrees ("300foo").is_err ());
    }

    #[test]
    fn parses_number_list () {
        assert_eq! (number_list ("5", ListLength::Exact (1)),
                    Ok (vec! [5.0]));

        assert_eq! (number_list ("1 2 3 4", ListLength::Exact (4)),
                    Ok (vec! [1.0, 2.0, 3.0, 4.0]));

        assert_eq! (number_list ("5", ListLength::Maximum (1)),
                    Ok (vec! [5.0]));

        assert_eq! (number_list ("1.0, -2.5", ListLength::Maximum (2)),
                    Ok (vec! [1.0, -2.5]));

        assert_eq! (number_list ("5 6", ListLength::Maximum (3)),
                    Ok (vec! [5.0, 6.0]));
    }

    #[test]
    fn errors_on_invalid_number_list () {
        // empty
        assert! (number_list ("", ListLength::Exact (1)).is_err ());

        // garbage
        assert! (number_list ("foo", ListLength::Exact (1)).is_err ());
        assert! (number_list ("1foo", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 foo", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 foo 2", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1,foo", ListLength::Exact (2)).is_err ());

        // too many
        assert! (number_list ("1 2", ListLength::Exact (1)).is_err ());
        assert! (number_list ("1,2,3", ListLength::Maximum (2)).is_err ());

        // extra token
        assert! (number_list ("1,", ListLength::Exact (1)).is_err ());

        // too few
        assert! (number_list ("1", ListLength::Exact (2)).is_err ());
        assert! (number_list ("1 2", ListLength::Exact (3)).is_err ());
    }
}
<|endoftext|>


<commit_before><commit_msg>Remove orphan bstr.rs<commit_after><|endoftext|>


<commit_before>use ring::constant_time::verify_slices_are_equal;
use ring::{hmac, signature};
use simple_asn1::BigUint;

use crate::algorithms::Algorithm;
use crate::errors::Result;
use crate::pem_decoder::PemEncodedKey;
use crate::serialization::{decode, encode};

pub(crate) mod ecdsa;
pub(crate) mod rsa;

/// The actual HS signing + encoding
pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &str) -> Result<String> {
    let digest = hmac::sign(&hmac::Key::new(alg, key), message.as_bytes());
    Ok(encode(digest.as_ref()))
}

/// Take the payload of a JWT, sign it using the algorithm given and return
/// the base64 url safe encoded of the result.
///
/// Only use this function if you want to do something other than JWT.
/// `key` is the secret for HMAC and a pem encoded string otherwise
pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result<String> {
    match algorithm {
        Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, message),
        Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, message),
        Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key, message),

        Algorithm::ES256 => ecdsa::sign(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, message),
        Algorithm::ES384 => ecdsa::sign(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, message),

        Algorithm::RS256 => rsa::sign(&signature::RSA_PKCS1_SHA256, key, message),
        Algorithm::RS384 => rsa::sign(&signature::RSA_PKCS1_SHA384, key, message),
        Algorithm::RS512 => rsa::sign(&signature::RSA_PKCS1_SHA512, key, message),

        Algorithm::PS256 => rsa::sign(&signature::RSA_PSS_SHA256, key, message),
        Algorithm::PS384 => rsa::sign(&signature::RSA_PSS_SHA384, key, message),
        Algorithm::PS512 => rsa::sign(&signature::RSA_PSS_SHA512, key, message),
    }
}

fn rsa_alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters {
    match alg {
        Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256,
        Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384,
        Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512,
        Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256,
        Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384,
        Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512,
        _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"),
    }
}

/// Compares the signature given with a re-computed signature for HMAC or using the public key
/// for RSA.
///
/// Only use this function if you want to do something other than JWT.
///
/// `signature` is the signature part of a jwt (text after the second '.')
///
/// `signing_input` is base64(header) + "." + base64(claims)
/// For ECDSA/RSS, the `key` is the pem public key
pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) -> Result<bool> {
    match algorithm {
        Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
            // we just re-sign the message with the key and compare if they are equal
            let signed = sign(message, key, algorithm)?;
            Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
        }
        Algorithm::ES256 => {
            verify_ring_es(&signature::ECDSA_P256_SHA256_FIXED, signature, message, key)
        }
        Algorithm::ES384 => {
            verify_ring_es(&signature::ECDSA_P384_SHA384_FIXED, signature, message, key)
        }
        _ => verify_ring_rsa(rsa_alg_to_rsa_parameters(algorithm), signature, message, key),
    }
}

// TODO: see if we can remove stuff?

/// See Ring docs for more details
fn verify_ring(
    alg: &'static dyn signature::VerificationAlgorithm,
    signature: &str,
    message: &str,
    key: &[u8],
) -> Result<bool> {
    let signature_bytes = decode(signature)?;
    let public_key = signature::UnparsedPublicKey::new(alg, key);
    let res = public_key.verify(message.as_bytes(), &signature_bytes);

    Ok(res.is_ok())
}

fn verify_ring_es(
    alg: &'static dyn signature::VerificationAlgorithm,
    signature: &str,
    message: &str,
    key: &[u8],
) -> Result<bool> {
    let pem_key = PemEncodedKey::new(key)?;
    verify_ring(alg, signature, message, pem_key.as_ec_public_key()?)
}

fn verify_ring_rsa(
    alg: &'static signature::RsaParameters,
    signature: &str,
    message: &str,
    key: &[u8],
) -> Result<bool> {
    let pem_key = PemEncodedKey::new(key)?;
    verify_ring(alg, signature, message, pem_key.as_rsa_key()?)
}

pub fn verify_rsa_modulus_exponent(
    alg: Algorithm,
    signature: &str,
    message: &str,
    components: (&str, &str),
) -> Result<bool> {
    let n = BigUint::from_bytes_be(&decode(components.0)?).to_bytes_be();
    let e = BigUint::from_bytes_be(&decode(components.1)?).to_bytes_be();
    let pubkey = signature::RsaPublicKeyComponents { n, e };
    let res = pubkey.verify(rsa_alg_to_rsa_parameters(alg), message.as_ref(), signature.as_ref());
    Ok(res.is_ok())
}
<commit_msg>Fix stupid bug<commit_after>use ring::constant_time::verify_slices_are_equal;
use ring::{hmac, signature};
use simple_asn1::BigUint;

use crate::algorithms::Algorithm;
use crate::errors::Result;
use crate::pem_decoder::PemEncodedKey;
use crate::serialization::{decode, encode};

pub(crate) mod ecdsa;
pub(crate) mod rsa;

/// The actual HS signing + encoding
pub(crate) fn sign_hmac(alg: hmac::Algorithm, key: &[u8], message: &str) -> Result<String> {
    let digest = hmac::sign(&hmac::Key::new(alg, key), message.as_bytes());
    Ok(encode(digest.as_ref()))
}

/// Take the payload of a JWT, sign it using the algorithm given and return
/// the base64 url safe encoded of the result.
///
/// Only use this function if you want to do something other than JWT.
/// `key` is the secret for HMAC and a pem encoded string otherwise
pub fn sign(message: &str, key: &[u8], algorithm: Algorithm) -> Result<String> {
    match algorithm {
        Algorithm::HS256 => sign_hmac(hmac::HMAC_SHA256, key, message),
        Algorithm::HS384 => sign_hmac(hmac::HMAC_SHA384, key, message),
        Algorithm::HS512 => sign_hmac(hmac::HMAC_SHA512, key, message),

        Algorithm::ES256 => ecdsa::sign(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, key, message),
        Algorithm::ES384 => ecdsa::sign(&signature::ECDSA_P384_SHA384_FIXED_SIGNING, key, message),

        Algorithm::RS256 => rsa::sign(&signature::RSA_PKCS1_SHA256, key, message),
        Algorithm::RS384 => rsa::sign(&signature::RSA_PKCS1_SHA384, key, message),
        Algorithm::RS512 => rsa::sign(&signature::RSA_PKCS1_SHA512, key, message),

        Algorithm::PS256 => rsa::sign(&signature::RSA_PSS_SHA256, key, message),
        Algorithm::PS384 => rsa::sign(&signature::RSA_PSS_SHA384, key, message),
        Algorithm::PS512 => rsa::sign(&signature::RSA_PSS_SHA512, key, message),
    }
}

fn rsa_alg_to_rsa_parameters(alg: Algorithm) -> &'static signature::RsaParameters {
    match alg {
        Algorithm::RS256 => &signature::RSA_PKCS1_2048_8192_SHA256,
        Algorithm::RS384 => &signature::RSA_PKCS1_2048_8192_SHA384,
        Algorithm::RS512 => &signature::RSA_PKCS1_2048_8192_SHA512,
        Algorithm::PS256 => &signature::RSA_PSS_2048_8192_SHA256,
        Algorithm::PS384 => &signature::RSA_PSS_2048_8192_SHA384,
        Algorithm::PS512 => &signature::RSA_PSS_2048_8192_SHA512,
        _ => unreachable!("Tried to get RSA signature for a non-rsa algorithm"),
    }
}

/// Compares the signature given with a re-computed signature for HMAC or using the public key
/// for RSA.
///
/// Only use this function if you want to do something other than JWT.
///
/// `signature` is the signature part of a jwt (text after the second '.')
///
/// `signing_input` is base64(header) + "." + base64(claims)
/// For ECDSA/RSS, the `key` is the pem public key
pub fn verify(signature: &str, message: &str, key: &[u8], algorithm: Algorithm) -> Result<bool> {
    match algorithm {
        Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
            // we just re-sign the message with the key and compare if they are equal
            let signed = sign(message, key, algorithm)?;
            Ok(verify_slices_are_equal(signature.as_ref(), signed.as_ref()).is_ok())
        }
        Algorithm::ES256 => {
            verify_ring_es(&signature::ECDSA_P256_SHA256_FIXED, signature, message, key)
        }
        Algorithm::ES384 => {
            verify_ring_es(&signature::ECDSA_P384_SHA384_FIXED, signature, message, key)
        }
        _ => verify_ring_rsa(rsa_alg_to_rsa_parameters(algorithm), signature, message, key),
    }
}

// TODO: see if we can remove stuff?

/// See Ring docs for more details
fn verify_ring(
    alg: &'static dyn signature::VerificationAlgorithm,
    signature: &str,
    message: &str,
    key: &[u8],
) -> Result<bool> {
    let signature_bytes = decode(signature)?;
    let public_key = signature::UnparsedPublicKey::new(alg, key);
    let res = public_key.verify(message.as_bytes(), &signature_bytes);

    Ok(res.is_ok())
}

fn verify_ring_es(
    alg: &'static dyn signature::VerificationAlgorithm,
    signature: &str,
    message: &str,
    key: &[u8],
) -> Result<bool> {
    let pem_key = PemEncodedKey::new(key)?;
    verify_ring(alg, signature, message, pem_key.as_ec_public_key()?)
}

fn verify_ring_rsa(
    alg: &'static signature::RsaParameters,
    signature: &str,
    message: &str,
    key: &[u8],
) -> Result<bool> {
    let pem_key = PemEncodedKey::new(key)?;
    verify_ring(alg, signature, message, pem_key.as_rsa_key()?)
}

pub fn verify_rsa_modulus_exponent(
    alg: Algorithm,
    signature: &str,
    message: &str,
    components: (&str, &str),
) -> Result<bool> {
    let signature_bytes = decode(signature)?;
    let n = BigUint::from_bytes_be(&decode(components.0)?).to_bytes_be();
    let e = BigUint::from_bytes_be(&decode(components.1)?).to_bytes_be();
    let pubkey = signature::RsaPublicKeyComponents { n, e };
    let res = pubkey.verify(rsa_alg_to_rsa_parameters(alg), message.as_ref(), &signature_bytes);
    Ok(res.is_ok())
}
<|endoftext|>


<commit_before>//! # descriptor
//!
//! Loads the tasks descriptor.

//! It will first load the default descriptor which is defined in cargo-make internally and
//! afterwards tries to find the external descriptor and load it as well.

//! If an extenal descriptor exists, it will be loaded and extend the default descriptor.
//!

#[cfg(test)]
#[path = "./descriptor_test.rs"]
mod descriptor_test;

use command;
use indexmap::IndexMap;
use std::collections::BTreeMap;
use std::env;
use std::fs::{canonicalize, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use toml;
use types::{Config, ConfigSection, EnvValue, ExternalConfig, Task};

fn merge_env(
    base: &mut IndexMap<String, EnvValue>,
    extended: &mut IndexMap<String, EnvValue>,
) -> IndexMap<String, EnvValue> {
    let mut merged = IndexMap::<String, EnvValue>::new();

    for (key, value) in base.iter() {
        let key_str = key.to_string();
        let value_clone = value.clone();
        merged.insert(key_str, value_clone);
    }

    for (key, value) in extended.iter() {
        let key_str = key.to_string();
        let value_clone = value.clone();
        merged.insert(key_str, value_clone);
    }

    merged
}

fn merge_tasks(
    base: &mut IndexMap<String, Task>,
    extended: &mut IndexMap<String, Task>,
) -> IndexMap<String, Task> {
    let mut merged = IndexMap::<String, Task>::new();

    for (key, value) in base.iter() {
        let key_str = key.to_string();
        merged.insert(key_str, value.clone());
    }

    for (key, value) in extended.iter() {
        let key_str = key.to_string();
        let mut task = value.clone();

        task = match base.get(key) {
            Some(ref value) => {
                let mut merged_task = Task::new();

                merged_task.extend(value);
                merged_task.extend(&task);

                merged_task
            }
            _ => task,
        };

        merged.insert(key_str, task);
    }

    merged
}

fn run_load_script(external_config: &ExternalConfig) -> bool {
    match external_config.config {
        Some(ref config) => {
            let load_script = config.get_load_script();

            match load_script {
                Some(ref script) => {
                    debug!("Load script found.");

                    command::run_script(script, None, &vec![], true);

                    true
                }
                None => {
                    debug!("No load script defined.");
                    false
                }
            }
        }
        None => {
            debug!("No load script defined.");
            false
        }
    }
}

fn merge_external_configs(config: ExternalConfig, parent_config: ExternalConfig) -> ExternalConfig {
    // merge env
    let mut parent_env = match parent_config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let mut extended_env = match config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let all_env = merge_env(&mut parent_env, &mut extended_env);

    // merge tasks
    let mut parent_tasks = match parent_config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let mut extended_tasks = match config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let all_tasks = merge_tasks(&mut parent_tasks, &mut extended_tasks);

    let mut config_section = ConfigSection::new();
    if parent_config.config.is_some() {
        config_section.extend(&mut parent_config.config.unwrap());
    }
    if config.config.is_some() {
        config_section.extend(&mut config.config.unwrap());
    }

    ExternalConfig {
        extend: None,
        config: Some(config_section),
        env: Some(all_env),
        tasks: Some(all_tasks),
    }
}

fn load_external_descriptor(base_path: &str, file_name: &str, set_env: bool) -> ExternalConfig {
    debug!(
        "Loading tasks from file: {} base directory: {}",
        &file_name, &base_path
    );

    let file_path = Path::new(base_path).join(file_name);

    if file_path.exists() {
        if set_env {
            let absolute_file_path = match canonicalize(&file_path) {
                Ok(result_path) => result_path,
                _ => file_path.clone(),
            };

            env::set_var("CARGO_MAKE_MAKEFILE_PATH", &absolute_file_path);
        }

        debug!("Opening file: {:#?}", &file_path);
        let mut file = match File::open(&file_path) {
            Ok(value) => value,
            Err(error) => panic!(
                "Unable to open file, base path: {} file name: {} error: {}",
                base_path, file_name, error
            ),
        };
        let mut external_descriptor = String::new();
        file.read_to_string(&mut external_descriptor).unwrap();

        let file_config: ExternalConfig = match toml::from_str(&external_descriptor) {
            Ok(value) => value,
            Err(error) => panic!("Unable to parse external descriptor, {}", error),
        };
        debug!("Loaded external config: {:#?}", &file_config);

        run_load_script(&file_config);

        match file_config.extend {
            Some(ref base_file) => {
                let parent_path_buf = Path::new(base_path).join(file_name).join("..");
                let parent_path = file_path
                    .parent()
                    .unwrap_or(&parent_path_buf)
                    .to_str()
                    .unwrap_or(".");
                debug!("External config parent path: {}", &parent_path);
                let base_file_config = load_external_descriptor(parent_path, base_file, false);

                // merge configs
                merge_external_configs(file_config.clone(), base_file_config)
            }
            None => file_config,
        }
    } else {
        info!("External file not found, skipping.");

        ExternalConfig::new()
    }
}

fn load_default(stable: bool, experimental: bool) -> Config {
    debug!("Loading base tasks.");

    let base_descriptor = if stable {
        include_str!("Makefile.stable.toml")
    } else {
        include_str!("Makefile.base.toml")
    };

    let mut base_config: Config = match toml::from_str(base_descriptor) {
        Ok(value) => value,
        Err(error) => panic!("Unable to parse base descriptor, {}", error),
    };
    debug!("Loaded base config: {:#?}", &base_config);

    if experimental {
        debug!("Loading experimental tasks.");
        let experimental_descriptor = include_str!("Makefile.beta.toml");

        let experimental_config: Config = match toml::from_str(experimental_descriptor) {
            Ok(value) => value,
            Err(error) => panic!("Unable to parse experimental descriptor, {}", error),
        };
        debug!("Loaded experimental config: {:#?}", &experimental_config);

        let mut base_tasks = base_config.tasks;
        let mut experimental_tasks = experimental_config.tasks;
        let all_tasks = merge_tasks(&mut base_tasks, &mut experimental_tasks);

        base_config.tasks = all_tasks;
    }

    base_config
}

/// Loads the tasks descriptor.

/// It will first load the default descriptor which is defined in cargo-make internally and
/// afterwards tries to find the external descriptor and load it as well.

/// If an extenal descriptor exists, it will be loaded and extend the default descriptor.
fn load_descriptors(
    file_name: &str,
    env_map: Option<Vec<String>>,
    stable: bool,
    experimental: bool,
) -> Config {
    let default_config = load_default(stable, experimental);

    let mut external_config: ExternalConfig = load_external_descriptor(".", file_name, true);

    external_config = match env::var("CARGO_MAKE_WORKSPACE_MAKEFILE") {
        Ok(workspace_makefile) => {
            let mut pathbuf = PathBuf::from(workspace_makefile);
            match pathbuf.clone().file_name() {
                Some(workspace_file_name) => match workspace_file_name.to_str() {
                    Some(workspace_file_name_str) => {
                        pathbuf.pop();

                        match pathbuf.to_str() {
                            Some(directory) => {
                                let workspace_config = load_external_descriptor(
                                    directory,
                                    workspace_file_name_str,
                                    false,
                                );
                                merge_external_configs(external_config, workspace_config)
                            }
                            _ => external_config,
                        }
                    }
                    _ => external_config,
                },
                _ => external_config,
            }
        }
        _ => external_config,
    };

    let mut external_tasks = match external_config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let mut default_tasks = default_config.tasks;

    let mut external_env = match external_config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let mut default_env = default_config.env;

    // merge configs
    let mut all_env = merge_env(&mut default_env, &mut external_env);
    all_env = match env_map {
        Some(values) => {
            let mut cli_env = IndexMap::new();

            for env_pair in &values {
                let env_part: Vec<&str> = env_pair.split('=').collect();
                debug!("Checking env pair: {}", &env_pair);

                if env_part.len() == 2 {
                    cli_env.insert(
                        env_part[0].to_string(),
                        EnvValue::Value(env_part[1].to_string()),
                    );
                }
            }

            merge_env(&mut all_env, &mut cli_env)
        }
        None => all_env,
    };

    let all_tasks = merge_tasks(&mut default_tasks, &mut external_tasks);

    let mut config_section = default_config.config.clone();
    config_section.extend(&mut external_config.config.unwrap_or(ConfigSection::new()));

    let config = Config {
        config: config_section,
        env: all_env,
        tasks: all_tasks,
    };

    debug!("Loaded merged config: {:#?}", &config);

    config
}

/// Loads the tasks descriptor.

/// It will first load the default descriptor which is defined in cargo-make internally and
/// afterwards tries to find the external descriptor and load it as well.

/// If an extenal descriptor exists, it will be loaded and extend the default descriptor.
pub(crate) fn load(file_name: &str, env_map: Option<Vec<String>>, experimental: bool) -> Config {
    let mut config = load_descriptors(&file_name, env_map.clone(), true, experimental);

    if config.config.skip_core_tasks.unwrap_or(false) {
        config = load_descriptors(&file_name, env_map.clone(), false, false);
    }

    config
}

pub(crate) fn list_steps(config: &Config) -> u32 {
    let mut count = 0;

    let mut categories = BTreeMap::new();

    for (key, value) in config.tasks.iter() {
        let is_private = match value.private {
            Some(private) => private,
            None => false,
        };

        if !is_private {
            count = count + 1;

            let category = match value.category {
                Some(ref value) => value,
                None => "No Category",
            };

            let description = match value.description {
                Some(ref value) => value,
                None => "No Description.",
            };

            let mut tasks_map = BTreeMap::new();
            match categories.get(category) {
                Some(value) => tasks_map.append(value),
                _ => (),
            };

            tasks_map.insert(key.clone(), description.clone());
            categories.insert(category, tasks_map);
        }
    }

    for (category, tasks) in &categories {
        println!("{}\n----------", category);

        for (key, description) in tasks {
            println!("{}: {} ", &key, &description);
        }
        println!("");
    }

    count
}
<commit_msg>fix lint issue<commit_after>//! # descriptor
//!
//! Loads the tasks descriptor.

//! It will first load the default descriptor which is defined in cargo-make internally and
//! afterwards tries to find the external descriptor and load it as well.

//! If an extenal descriptor exists, it will be loaded and extend the default descriptor.
//!

#[cfg(test)]
#[path = "./descriptor_test.rs"]
mod descriptor_test;

use command;
use indexmap::IndexMap;
use std::collections::BTreeMap;
use std::env;
use std::fs::{canonicalize, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use toml;
use types::{Config, ConfigSection, EnvValue, ExternalConfig, Task};

fn merge_env(
    base: &mut IndexMap<String, EnvValue>,
    extended: &mut IndexMap<String, EnvValue>,
) -> IndexMap<String, EnvValue> {
    let mut merged = IndexMap::<String, EnvValue>::new();

    for (key, value) in base.iter() {
        let key_str = key.to_string();
        let value_clone = value.clone();
        merged.insert(key_str, value_clone);
    }

    for (key, value) in extended.iter() {
        let key_str = key.to_string();
        let value_clone = value.clone();
        merged.insert(key_str, value_clone);
    }

    merged
}

fn merge_tasks(
    base: &mut IndexMap<String, Task>,
    extended: &mut IndexMap<String, Task>,
) -> IndexMap<String, Task> {
    let mut merged = IndexMap::<String, Task>::new();

    for (key, value) in base.iter() {
        let key_str = key.to_string();
        merged.insert(key_str, value.clone());
    }

    for (key, value) in extended.iter() {
        let key_str = key.to_string();
        let mut task = value.clone();

        task = match base.get(key) {
            Some(ref value) => {
                let mut merged_task = Task::new();

                merged_task.extend(value);
                merged_task.extend(&task);

                merged_task
            }
            _ => task,
        };

        merged.insert(key_str, task);
    }

    merged
}

fn run_load_script(external_config: &ExternalConfig) -> bool {
    match external_config.config {
        Some(ref config) => {
            let load_script = config.get_load_script();

            match load_script {
                Some(ref script) => {
                    debug!("Load script found.");

                    command::run_script(script, None, &vec![], true);

                    true
                }
                None => {
                    debug!("No load script defined.");
                    false
                }
            }
        }
        None => {
            debug!("No load script defined.");
            false
        }
    }
}

fn merge_external_configs(config: ExternalConfig, parent_config: ExternalConfig) -> ExternalConfig {
    // merge env
    let mut parent_env = match parent_config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let mut extended_env = match config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let all_env = merge_env(&mut parent_env, &mut extended_env);

    // merge tasks
    let mut parent_tasks = match parent_config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let mut extended_tasks = match config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let all_tasks = merge_tasks(&mut parent_tasks, &mut extended_tasks);

    let mut config_section = ConfigSection::new();
    if parent_config.config.is_some() {
        config_section.extend(&mut parent_config.config.unwrap());
    }
    if config.config.is_some() {
        config_section.extend(&mut config.config.unwrap());
    }

    ExternalConfig {
        extend: None,
        config: Some(config_section),
        env: Some(all_env),
        tasks: Some(all_tasks),
    }
}

fn load_external_descriptor(base_path: &str, file_name: &str, set_env: bool) -> ExternalConfig {
    debug!(
        "Loading tasks from file: {} base directory: {}",
        &file_name, &base_path
    );

    let file_path = Path::new(base_path).join(file_name);

    if file_path.exists() {
        if set_env {
            let absolute_file_path = match canonicalize(&file_path) {
                Ok(result_path) => result_path,
                _ => file_path.clone(),
            };

            env::set_var("CARGO_MAKE_MAKEFILE_PATH", &absolute_file_path);
        }

        debug!("Opening file: {:#?}", &file_path);
        let mut file = match File::open(&file_path) {
            Ok(value) => value,
            Err(error) => panic!(
                "Unable to open file, base path: {} file name: {} error: {}",
                base_path, file_name, error
            ),
        };
        let mut external_descriptor = String::new();
        file.read_to_string(&mut external_descriptor).unwrap();

        let file_config: ExternalConfig = match toml::from_str(&external_descriptor) {
            Ok(value) => value,
            Err(error) => panic!("Unable to parse external descriptor, {}", error),
        };
        debug!("Loaded external config: {:#?}", &file_config);

        run_load_script(&file_config);

        match file_config.extend {
            Some(ref base_file) => {
                let parent_path_buf = Path::new(base_path).join(file_name).join("..");
                let parent_path = file_path
                    .parent()
                    .unwrap_or(&parent_path_buf)
                    .to_str()
                    .unwrap_or(".");
                debug!("External config parent path: {}", &parent_path);
                let base_file_config = load_external_descriptor(parent_path, base_file, false);

                // merge configs
                merge_external_configs(file_config.clone(), base_file_config)
            }
            None => file_config,
        }
    } else {
        info!("External file not found, skipping.");

        ExternalConfig::new()
    }
}

fn load_default(stable: bool, experimental: bool) -> Config {
    debug!("Loading base tasks.");

    let base_descriptor = if stable {
        include_str!("Makefile.stable.toml")
    } else {
        include_str!("Makefile.base.toml")
    };

    let mut base_config: Config = match toml::from_str(base_descriptor) {
        Ok(value) => value,
        Err(error) => panic!("Unable to parse base descriptor, {}", error),
    };
    debug!("Loaded base config: {:#?}", &base_config);

    if experimental {
        debug!("Loading experimental tasks.");
        let experimental_descriptor = include_str!("Makefile.beta.toml");

        let experimental_config: Config = match toml::from_str(experimental_descriptor) {
            Ok(value) => value,
            Err(error) => panic!("Unable to parse experimental descriptor, {}", error),
        };
        debug!("Loaded experimental config: {:#?}", &experimental_config);

        let mut base_tasks = base_config.tasks;
        let mut experimental_tasks = experimental_config.tasks;
        let all_tasks = merge_tasks(&mut base_tasks, &mut experimental_tasks);

        base_config.tasks = all_tasks;
    }

    base_config
}

/// Loads the tasks descriptor.

/// It will first load the default descriptor which is defined in cargo-make internally and
/// afterwards tries to find the external descriptor and load it as well.

/// If an extenal descriptor exists, it will be loaded and extend the default descriptor.
fn load_descriptors(
    file_name: &str,
    env_map: Option<Vec<String>>,
    stable: bool,
    experimental: bool,
) -> Config {
    let default_config = load_default(stable, experimental);

    let mut external_config: ExternalConfig = load_external_descriptor(".", file_name, true);

    external_config = match env::var("CARGO_MAKE_WORKSPACE_MAKEFILE") {
        Ok(workspace_makefile) => {
            let mut pathbuf = PathBuf::from(workspace_makefile);
            match pathbuf.clone().file_name() {
                Some(workspace_file_name) => match workspace_file_name.to_str() {
                    Some(workspace_file_name_str) => {
                        pathbuf.pop();

                        match pathbuf.to_str() {
                            Some(directory) => {
                                let workspace_config = load_external_descriptor(
                                    directory,
                                    workspace_file_name_str,
                                    false,
                                );
                                merge_external_configs(external_config, workspace_config)
                            }
                            _ => external_config,
                        }
                    }
                    _ => external_config,
                },
                _ => external_config,
            }
        }
        _ => external_config,
    };

    let mut external_tasks = match external_config.tasks {
        Some(tasks) => tasks,
        None => IndexMap::new(),
    };
    let mut default_tasks = default_config.tasks;

    let mut external_env = match external_config.env {
        Some(env) => env,
        None => IndexMap::new(),
    };
    let mut default_env = default_config.env;

    // merge configs
    let mut all_env = merge_env(&mut default_env, &mut external_env);
    all_env = match env_map {
        Some(values) => {
            let mut cli_env = IndexMap::new();

            for env_pair in &values {
                let env_part: Vec<&str> = env_pair.split('=').collect();
                debug!("Checking env pair: {}", &env_pair);

                if env_part.len() == 2 {
                    cli_env.insert(
                        env_part[0].to_string(),
                        EnvValue::Value(env_part[1].to_string()),
                    );
                }
            }

            merge_env(&mut all_env, &mut cli_env)
        }
        None => all_env,
    };

    let all_tasks = merge_tasks(&mut default_tasks, &mut external_tasks);

    let mut config_section = default_config.config.clone();
    config_section.extend(&mut external_config.config.unwrap_or(ConfigSection::new()));

    let config = Config {
        config: config_section,
        env: all_env,
        tasks: all_tasks,
    };

    debug!("Loaded merged config: {:#?}", &config);

    config
}

/// Loads the tasks descriptor.

/// It will first load the default descriptor which is defined in cargo-make internally and
/// afterwards tries to find the external descriptor and load it as well.

/// If an extenal descriptor exists, it will be loaded and extend the default descriptor.
pub(crate) fn load(file_name: &str, env_map: Option<Vec<String>>, experimental: bool) -> Config {
    let mut config = load_descriptors(&file_name, env_map.clone(), true, experimental);

    if config.config.skip_core_tasks.unwrap_or(false) {
        config = load_descriptors(&file_name, env_map.clone(), false, false);
    }

    config
}

pub(crate) fn list_steps(config: &Config) -> u32 {
    let mut count = 0;

    let mut categories = BTreeMap::new();

    for (key, value) in config.tasks.iter() {
        let is_private = match value.private {
            Some(private) => private,
            None => false,
        };

        if !is_private {
            count = count + 1;

            let category = match value.category {
                Some(ref value) => value,
                None => "No Category",
            };

            let description = match value.description {
                Some(ref value) => value,
                None => "No Description.",
            };

            let mut tasks_map = BTreeMap::new();
            match categories.get_mut(category) {
                Some(mut value) => tasks_map.append(value),
                _ => (),
            };

            tasks_map.insert(key.clone(), description.clone());
            categories.insert(category, tasks_map);
        }
    }

    for (category, tasks) in &categories {
        println!("{}\n----------", category);

        for (key, description) in tasks {
            println!("{}: {} ", &key, &description);
        }
        println!("");
    }

    count
}
<|endoftext|>