<commit_before>use std::os;
use std::path::Path;
use std::io;
use std::io::process::{Process,ProcessConfig,ProcessOutput,InheritFd};
use collections::HashMap;
use core::errors::{ToResult,CargoResult,CargoError};

#[deriving(Clone,Eq)]
pub struct ProcessBuilder {
    program: ~str,
    args: Vec<~str>,
    path: Vec<~str>,
    env: HashMap<~str, ~str>,
    cwd: Path
}

// TODO: Upstream a Windows/Posix branch to Rust proper
static PATH_SEP : &'static str = ":";

impl ProcessBuilder {
    pub fn args(mut self, arguments: &[~str]) -> ProcessBuilder {
        self.args = Vec::from_slice(arguments);
        self
    }

    pub fn extra_path(mut self, path: Path) -> ProcessBuilder {
        // For now, just convert to a string, but we should do something better
        self.path.push(format!("{}", path.display()));
        self
    }

    pub fn cwd(mut self, path: Path) -> ProcessBuilder {
        self.cwd = path;
        self
    }

    // TODO: should InheritFd be hardcoded?
    pub fn exec(&self) -> io::IoResult<()> {
        let mut config = try!(self.build_config());
        let env = self.build_env();

        // Set where the output goes
        config.env = Some(env.as_slice());
        config.stdout = InheritFd(1);
        config.stderr = InheritFd(2);

        let mut process = try!(Process::configure(config));
        let exit = process.wait();

        if exit.success() {
            Ok(())
        }
        else {
            Err(io::IoError {
                kind: io::OtherIoError,
                desc: "process did not exit successfully",
                detail: None
            })
        }
    }

    // TODO: Match exec()
    pub fn exec_with_output(&self) -> CargoResult<ProcessOutput> {
        let mut config = ProcessConfig::new();

        config.program = self.program.as_slice();
        config.args = self.args.as_slice();
        config.cwd = Some(&self.cwd);

        let os_path = try!(os::getenv("PATH").to_result(|_|
            CargoError::described("Could not find the PATH environment variable")));

        let path = os_path + PATH_SEP + self.path.connect(PATH_SEP);

        let path = [("PATH".to_owned(), path)];
        config.env = Some(path.as_slice());

        Process::configure(config).map(|mut ok| ok.wait_with_output()).to_result(|_|
            CargoError::described("Could not spawn process"))
    }

    fn build_config<'a>(&'a self) -> io::IoResult<ProcessConfig<'a>> {
        let mut config = ProcessConfig::new();

        config.program = self.program.as_slice();
        config.args = self.args.as_slice();
        config.cwd = Some(&self.cwd);

        Ok(config)
    }

    fn build_env(&self) -> ~[(~str, ~str)] {
        let mut ret = Vec::new();

        for (key, val) in self.env.iter() {
            // Skip path
            if key.as_slice() != "PATH" {
                ret.push((key.clone(), val.clone()));
            }
        }

        match self.build_path() {
            Some(path) => ret.push(("PATH".to_owned(), path)),
            _ => ()
        }

        ret.as_slice().to_owned()
    }

    fn build_path(&self) -> Option<~str> {
        let path = self.path.connect(PATH_SEP);

        match self.env.find_equiv(&("PATH")) {
            Some(existing) => {
                if self.path.is_empty() {
                    Some(existing.to_owned())
                }
                else {
                    Some(existing.as_slice() + PATH_SEP + path)
                }
            }
            None => {
                if self.path.is_empty() {
                    None
                }
                else {
                    Some(path)
                }
            }
        }
    }
}

pub fn process(cmd: &str) -> ProcessBuilder {
    ProcessBuilder {
        program: cmd.to_owned(),
        args: vec!(),
        path: vec!(),
        cwd: os::getcwd(),
        env: system_env()
    }
}

fn system_env() -> HashMap<~str, ~str> {
    let mut ret = HashMap::new();

    for &(ref key, ref val) in os::env().iter() {
        ret.insert(key.clone(), val.clone());
    }

    ret
}
<commit_msg>Cleanup ProcessBuilder<commit_after>use std::os;
use std::path::Path;
use std::io;
use std::io::process::{Process,ProcessConfig,ProcessOutput,InheritFd};
use collections::HashMap;

#[deriving(Clone,Eq)]
pub struct ProcessBuilder {
    program: ~str,
    args: Vec<~str>,
    path: Vec<~str>,
    env: HashMap<~str, ~str>,
    cwd: Path
}

// TODO: Upstream a Windows/Posix branch to Rust proper
static PATH_SEP : &'static str = ":";

impl ProcessBuilder {
    pub fn args(mut self, arguments: &[~str]) -> ProcessBuilder {
        self.args = Vec::from_slice(arguments);
        self
    }

    pub fn extra_path(mut self, path: Path) -> ProcessBuilder {
        // For now, just convert to a string, but we should do something better
        self.path.push(format!("{}", path.display()));
        self
    }

    pub fn cwd(mut self, path: Path) -> ProcessBuilder {
        self.cwd = path;
        self
    }

    // TODO: should InheritFd be hardcoded?
    pub fn exec(&self) -> io::IoResult<()> {
        let mut config = try!(self.build_config());
        let env = self.build_env();

        // Set where the output goes
        config.env = Some(env.as_slice());
        config.stdout = InheritFd(1);
        config.stderr = InheritFd(2);

        let mut process = try!(Process::configure(config));
        let exit = process.wait();

        if exit.success() {
            Ok(())
        }
        else {
            Err(io::IoError {
                kind: io::OtherIoError,
                desc: "process did not exit successfully",
                detail: None
            })
        }
    }

    pub fn exec_with_output(&self) -> io::IoResult<ProcessOutput> {
        let mut config = try!(self.build_config());
        let env = self.build_env();

        config.env = Some(env.as_slice());

        Process::configure(config).map(|mut ok| ok.wait_with_output())
    }

    fn build_config<'a>(&'a self) -> io::IoResult<ProcessConfig<'a>> {
        let mut config = ProcessConfig::new();

        config.program = self.program.as_slice();
        config.args = self.args.as_slice();
        config.cwd = Some(&self.cwd);

        Ok(config)
    }

    fn build_env(&self) -> ~[(~str, ~str)] {
        let mut ret = Vec::new();

        for (key, val) in self.env.iter() {
            // Skip path
            if key.as_slice() != "PATH" {
                ret.push((key.clone(), val.clone()));
            }
        }

        match self.build_path() {
            Some(path) => ret.push(("PATH".to_owned(), path)),
            _ => ()
        }

        ret.as_slice().to_owned()
    }

    fn build_path(&self) -> Option<~str> {
        let path = self.path.connect(PATH_SEP);

        match self.env.find_equiv(&("PATH")) {
            Some(existing) => {
                if self.path.is_empty() {
                    Some(existing.to_owned())
                }
                else {
                    Some(existing.as_slice() + PATH_SEP + path)
                }
            }
            None => {
                if self.path.is_empty() {
                    None
                }
                else {
                    Some(path)
                }
            }
        }
    }
}

pub fn process(cmd: &str) -> ProcessBuilder {
    ProcessBuilder {
        program: cmd.to_owned(),
        args: vec!(),
        path: vec!(),
        cwd: os::getcwd(),
        env: system_env()
    }
}

fn system_env() -> HashMap<~str, ~str> {
    let mut ret = HashMap::new();

    for &(ref key, ref val) in os::env().iter() {
        ret.insert(key.clone(), val.clone());
    }

    ret
}
<|endoftext|>


<commit_before>use super::Command;

extern crate chrono;

use lib::fs::*;
use lib::setting::*;
use self::chrono::Local;
use std::fs;
use std::path::PathBuf;

pub struct Sweep;

impl Command for Sweep {
    fn validation(&self) -> bool { true }

    fn help_message(&self) -> &'static str {
        return "sweep!";
    }

    fn main(&self) {
        println!("Sweep ...\n");

        let now  = Local::now();
        let date = format!("{}", now.format("%Y-%m-%d"));

        let path_to_dust_box = path_buf![storage_dir(), date, "dust"];
        create_essential_dir_all(&path_to_dust_box);

        let ignore = read_ignore_file();

        let target_files = walk_dir(".")
            .difference(&ignore)
            .cloned()
            .collect::<Vec<String>>();

        for target in target_files.iter() {
            let target_path = path_buf![&target];
            let target_name = extract_file_name(&target_path);
            let target_base = extract_base(&target_path);
            let to          = path_buf![&path_to_dust_box, target_base];

            create_essential_dir_all(&to);

            println!(r#"EXECUTION: Move "{}" to dust box."#, target_name);

            // forcedly overwrite if the file exists with same name.
            match fs::rename(target, path_buf![to, target_name]) {
                Ok(_)    => println!("  OK: Moved the file to dust box."),
                Err(why) => panic!("  ERROR: {}", why),
            }
        }

        // delete all empty directories.
        let all_dirs = enumerate_only_dirs_under(".");
        for dir in all_dirs.iter().filter(|d| *d != ".") {
            println!(r#"EXECUTION: Remove "{}" directory."#, dir);

            match fs::remove_dir(dir) {
                Ok(_) => {
                    println!("  OK: Removed the directory.");
                    create_essential_dir_all(&path_buf![&path_to_dust_box, dir]);
                },
                Err(why) => match why.raw_os_error() {
                    Some(39) => println!("  NOTICE: the directory is not empty."),
                    _        => panic!("  ERROR: {}", why),
                },
            }
        }
    }
}
<commit_msg>Create two functions for readablity<commit_after>use super::Command;

extern crate chrono;

use lib::fs::*;
use lib::setting::*;
use self::chrono::Local;
use std::fs;
use std::path::PathBuf;

pub struct Sweep;

impl Command for Sweep {
    fn validation(&self) -> bool { true }

    fn help_message(&self) -> &'static str {
        return "sweep!";
    }

    fn main(&self) {
        println!("Sweep ...\n");

        let now  = Local::now();
        let date = format!("{}", now.format("%Y-%m-%d"));

        let path_to_dust_box = path_buf![storage_dir(), date, "dusts"];
        create_essential_dir_all(&path_to_dust_box);

        let ignore = read_ignore_file();

        let target_files = walk_dir(".")
            .difference(&ignore)
            .cloned()
            .collect::<Vec<String>>();
        move_files_to_dust_box(target_files, &path_to_dust_box);

        move_empty_dir_to_dust_box(&path_to_dust_box);
    }
}

fn move_files_to_dust_box(target_files: Vec<String>, path_to_dust_box: &PathBuf) {
    for target in target_files.iter() {
        let target_path = path_buf![&target];
        let target_name = extract_file_name(&target_path);
        let target_base = extract_base(&target_path);
        let to          = path_buf![&path_to_dust_box, target_base];

        create_essential_dir_all(&to);

        println!(r#"EXECUTION: Move "{}" to dust box."#, target_name);

        // forcedly overwrite if the file exists with same name.
        match fs::rename(target, path_buf![to, target_name]) {
            Ok(_)    => println!("  OK: Moved the file to dust box."),
            Err(why) => panic!("  ERROR: {}", why),
        }
    }
}

fn move_empty_dir_to_dust_box(path_to_dust_box: &PathBuf) {
    let all_dirs = enumerate_only_dirs_under(".");
    for dir in all_dirs.iter().filter(|d| *d != ".") {
        println!(r#"EXECUTION: Remove "{}" directory."#, dir);

        match fs::remove_dir(dir) {
            Ok(_) => {
                println!("  OK: Removed the directory.");
                create_essential_dir_all(&path_buf![&path_to_dust_box, dir]);
            },
            Err(why) => match why.raw_os_error() {
                Some(39) => println!("  NOTICE: the directory is not empty."),
                _        => panic!("  ERROR: {}", why),
            },
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

pub type Id = u32;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Bounds {
    x: i32,
    y: i32,
    width: i32,
    height: i32,
}
impl Bounds {
    pub fn intersects(&self, other: &Self) -> bool {
        ((self.x - other.x).abs() * 2 < (self.width + other.width))&&
            ((self.y - other.y).abs() * 2 < (self.height + other.height))
    }
    pub fn contains(&self, other: &Self) -> bool {
        other.x - other.width / 2 >= self.x - self.width / 2 &&
        other.y - other.height / 2 >= self.y - self.height / 2 &&
        other.x + other.width / 2 <= self.x + self.width / 2 &&
        other.y + other.height / 2 <= self.y + self.height / 2
    }
}

pub struct World {
    width: i32,
    height: i32,
    entities: HashMap<Id, Bounds>,
}

impl World {
    pub fn new(width: i32, height: i32) -> Self {
        World {width, height, entities: HashMap::new()}
    }
    pub fn insert(&mut self, id: Id, bounds: Bounds) -> Option<Bounds> {
        assert!(bounds.x + bounds.width / 2 > 0 &&
                bounds.y + bounds.height / 2 > 0 &&
                bounds.x - bounds.width / 2 < self.width &&
                bounds.y - bounds.height / 2 < self.height);
        self.entities.insert(id, bounds)
    }
    pub fn remove(&mut self, id: Id) -> Option<Bounds> {
        self.entities.remove(&id)
    }
    pub fn query_intersects<T>(&self, bounds: &Bounds, mut cb: T) where T: FnMut(Id, &Bounds) {
        for (id, entity) in self.entities.iter() {
            if entity.intersects(bounds) {
                cb(*id, entity);
            }
        }
    }
}

#[test]
fn intersects() {
    let a = Bounds{x: 0, y: 0, width: 10, height: 10};
    let b = Bounds{x: 5, y: 5, width: 10, height: 10};
    assert!(a.intersects(&b));
}

#[test]
fn doesnt_intersect() {
    let a = Bounds{x: 0, y: 0, width: 10, height: 10};
    let b = Bounds{x: 50, y: 50, width: 10, height: 10};
    assert!(!a.intersects(&b));
}

#[test]
fn doesnt_intersect_edge() {
    let a = Bounds{x: 0, y: 0, width: 10, height: 10};
    let b = Bounds{x: 15, y: 0, width: 10, height: 10};
    assert!(!a.intersects(&b));
}

#[test]
fn contains() {
    let a = Bounds{x: 0, y: 0, width: 100, height: 100};
    let b = Bounds{x: 2, y: 2, width: 10, height: 10};
    assert!(a.contains(&b));
}

#[test]
fn doesnt_contain() {
    let a = Bounds{x: 0, y: 0, width: 10, height: 10};
    let b = Bounds{x: 50, y: 50, width: 10, height: 10};
    assert!(!a.contains(&b));
}

#[test]
fn doesnt_contain_when_intersecting() {
    let a = Bounds{x: 0, y: 0, width: 10, height: 10};
    let b = Bounds{x: 5, y: 5, width: 10, height: 10};
    assert!(!a.contains(&b));
}

#[test]
fn contains_edge() {
    let a = Bounds{x: 0, y: 0, width: 10, height: 10};
    let b = Bounds{x: 10, y: 10, width: 0, height: 0};
    assert!(!a.contains(&b));
}

#[test]
fn contains_self() {
    let a = Bounds{x: 0, y: 0, width: 10, height: 10};
    assert!(a.contains(&a));
}

#[test]
fn init() {
    World::new(1000, 1000);
}

#[test]
fn insert_new() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let old = world.insert(1, bounds);
    assert!(old.is_none());
}

#[test]
#[should_panic]
fn insert_too_low() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: -10,
        y: -10,
        width: 1,
        height: 1,
    };
    world.insert(1, bounds);
}

#[test]
#[should_panic]
fn insert_too_low_edge() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: -10,
        y: 0,
        width: 5,
        height: 5,
    };
    world.insert(1, bounds);
}

#[test]
#[should_panic]
fn insert_too_high() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: 2000,
        y: 2000,
        width: 1,
        height: 1,
    };
    world.insert(1, bounds);
}

#[test]
#[should_panic]
fn insert_too_high_edge() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: 0,
        y: 1010,
        width: 1,
        height: 5,
    };
    world.insert(1, bounds);
}

#[test]
fn insert_existing() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds_a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let old = world.insert(id, bounds_a.clone());
    assert!(old.is_none());
    let bounds_b = Bounds {
        x: 1,
        y: 1,
        width: 10,
        height: 10,
    };
    let old = world.insert(id, bounds_b);
    assert_eq!(bounds_a, old.unwrap())
}

#[test]
fn remove() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(id, bounds.clone());
    let removed_bounds = world.remove(id);
    assert_eq!(bounds, removed_bounds.unwrap());
    let old = world.insert(id, bounds);
    assert!(old.is_none());
}

#[test]
fn remove_nonexistant() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(1, bounds);
    let removed = world.remove(2);
    assert!(removed.is_none())
}

#[test]
fn remove_empty() {
    let mut world = World::new(1000, 1000);
    let removed = world.remove(1);
    assert!(removed.is_none())
}

#[test]
fn remove_twice() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(id, bounds.clone());
    let removed = world.remove(id);
    assert_eq!(bounds, removed.unwrap());
    let removed = world.remove(id);
    assert!(removed.is_none());
}

#[test]
fn no_collisions() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds_a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(id, bounds_a.clone());
    let bounds_b = Bounds {
        x: 40,
        y: 40,
        width: 10,
        height: 10,
    };
    world.query_intersects(&bounds_b, |_, _| panic!());
}

#[test]
fn one_collision() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds_a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(id, bounds_a.clone());
    let bounds_b = Bounds {
        x: 5,
        y: 5,
        width: 10,
        height: 10,
    };
    let mut collisions = Vec::new();
    world.query_intersects(&bounds_b, |id, bounds| collisions.push((id, bounds.clone())));
    assert_eq!(1, collisions.len());
    let &(coll_id, ref coll_bounds) = collisions.first().unwrap();
    assert_eq!(id, coll_id);
    assert_eq!(bounds_a, *coll_bounds);
}
<commit_msg>Core: Run rustfmt<commit_after>use std::collections::HashMap;

pub type Id = u32;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Bounds {
    x: i32,
    y: i32,
    width: i32,
    height: i32,
}
impl Bounds {
    pub fn intersects(&self, other: &Self) -> bool {
        ((self.x - other.x).abs() * 2 < (self.width + other.width)) &&
            ((self.y - other.y).abs() * 2 < (self.height + other.height))
    }
    pub fn contains(&self, other: &Self) -> bool {
        other.x - other.width / 2 >= self.x - self.width / 2 &&
            other.y - other.height / 2 >= self.y - self.height / 2 &&
            other.x + other.width / 2 <= self.x + self.width / 2 &&
            other.y + other.height / 2 <= self.y + self.height / 2
    }
}

pub struct World {
    width: i32,
    height: i32,
    entities: HashMap<Id, Bounds>,
}

impl World {
    pub fn new(width: i32, height: i32) -> Self {
        World {
            width,
            height,
            entities: HashMap::new(),
        }
    }
    pub fn insert(&mut self, id: Id, bounds: Bounds) -> Option<Bounds> {
        assert!(
            bounds.x + bounds.width / 2 > 0 && bounds.y + bounds.height / 2 > 0 &&
                bounds.x - bounds.width / 2 < self.width &&
                bounds.y - bounds.height / 2 < self.height
        );
        self.entities.insert(id, bounds)
    }
    pub fn remove(&mut self, id: Id) -> Option<Bounds> {
        self.entities.remove(&id)
    }
    pub fn query_intersects<T>(&self, bounds: &Bounds, mut cb: T)
    where
        T: FnMut(Id, &Bounds),
    {
        for (id, entity) in self.entities.iter() {
            if entity.intersects(bounds) {
                cb(*id, entity);
            }
        }
    }
}

#[test]
fn intersects() {
    let a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let b = Bounds {
        x: 5,
        y: 5,
        width: 10,
        height: 10,
    };
    assert!(a.intersects(&b));
}

#[test]
fn doesnt_intersect() {
    let a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let b = Bounds {
        x: 50,
        y: 50,
        width: 10,
        height: 10,
    };
    assert!(!a.intersects(&b));
}

#[test]
fn doesnt_intersect_edge() {
    let a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let b = Bounds {
        x: 15,
        y: 0,
        width: 10,
        height: 10,
    };
    assert!(!a.intersects(&b));
}

#[test]
fn contains() {
    let a = Bounds {
        x: 0,
        y: 0,
        width: 100,
        height: 100,
    };
    let b = Bounds {
        x: 2,
        y: 2,
        width: 10,
        height: 10,
    };
    assert!(a.contains(&b));
}

#[test]
fn doesnt_contain() {
    let a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let b = Bounds {
        x: 50,
        y: 50,
        width: 10,
        height: 10,
    };
    assert!(!a.contains(&b));
}

#[test]
fn doesnt_contain_when_intersecting() {
    let a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let b = Bounds {
        x: 5,
        y: 5,
        width: 10,
        height: 10,
    };
    assert!(!a.contains(&b));
}

#[test]
fn contains_edge() {
    let a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let b = Bounds {
        x: 10,
        y: 10,
        width: 0,
        height: 0,
    };
    assert!(!a.contains(&b));
}

#[test]
fn contains_self() {
    let a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    assert!(a.contains(&a));
}

#[test]
fn init() {
    World::new(1000, 1000);
}

#[test]
fn insert_new() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let old = world.insert(1, bounds);
    assert!(old.is_none());
}

#[test]
#[should_panic]
fn insert_too_low() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: -10,
        y: -10,
        width: 1,
        height: 1,
    };
    world.insert(1, bounds);
}

#[test]
#[should_panic]
fn insert_too_low_edge() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: -10,
        y: 0,
        width: 5,
        height: 5,
    };
    world.insert(1, bounds);
}

#[test]
#[should_panic]
fn insert_too_high() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: 2000,
        y: 2000,
        width: 1,
        height: 1,
    };
    world.insert(1, bounds);
}

#[test]
#[should_panic]
fn insert_too_high_edge() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: 0,
        y: 1010,
        width: 1,
        height: 5,
    };
    world.insert(1, bounds);
}

#[test]
fn insert_existing() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds_a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    let old = world.insert(id, bounds_a.clone());
    assert!(old.is_none());
    let bounds_b = Bounds {
        x: 1,
        y: 1,
        width: 10,
        height: 10,
    };
    let old = world.insert(id, bounds_b);
    assert_eq!(bounds_a, old.unwrap())
}

#[test]
fn remove() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(id, bounds.clone());
    let removed_bounds = world.remove(id);
    assert_eq!(bounds, removed_bounds.unwrap());
    let old = world.insert(id, bounds);
    assert!(old.is_none());
}

#[test]
fn remove_nonexistant() {
    let mut world = World::new(1000, 1000);
    let bounds = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(1, bounds);
    let removed = world.remove(2);
    assert!(removed.is_none())
}

#[test]
fn remove_empty() {
    let mut world = World::new(1000, 1000);
    let removed = world.remove(1);
    assert!(removed.is_none())
}

#[test]
fn remove_twice() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(id, bounds.clone());
    let removed = world.remove(id);
    assert_eq!(bounds, removed.unwrap());
    let removed = world.remove(id);
    assert!(removed.is_none());
}

#[test]
fn no_collisions() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds_a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(id, bounds_a.clone());
    let bounds_b = Bounds {
        x: 40,
        y: 40,
        width: 10,
        height: 10,
    };
    world.query_intersects(&bounds_b, |_, _| panic!());
}

#[test]
fn one_collision() {
    let mut world = World::new(1000, 1000);
    let id = 1;
    let bounds_a = Bounds {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
    };
    world.insert(id, bounds_a.clone());
    let bounds_b = Bounds {
        x: 5,
        y: 5,
        width: 10,
        height: 10,
    };
    let mut collisions = Vec::new();
    world.query_intersects(
        &bounds_b,
        |id, bounds| collisions.push((id, bounds.clone())),
    );
    assert_eq!(1, collisions.len());
    let &(coll_id, ref coll_bounds) = collisions.first().unwrap();
    assert_eq!(id, coll_id);
    assert_eq!(bounds_a, *coll_bounds);
}
<|endoftext|>


<commit_before>//! # Virtual File System
//!
//! VFS stores all files read by rust-analyzer. Reading file contents from VFS
//! always returns the same contents, unless VFS was explicitly modified with
//! [`set_file_contents`]. All changes to VFS are logged, and can be retrieved via
//! [`take_changes`] method. The pack of changes is then pushed to `salsa` and
//! triggers incremental recomputation.
//!
//! Files in VFS are identified with [`FileId`]s -- interned paths. The notion of
//! the path, [`VfsPath`] is somewhat abstract: at the moment, it is represented
//! as an [`std::path::PathBuf`] internally, but this is an implementation detail.
//!
//! VFS doesn't do IO or file watching itself. For that, see the [`loader`]
//! module. [`loader::Handle`] is an object-safe trait which abstracts both file
//! loading and file watching. [`Handle`] is dynamically configured with a set of
//! directory entries which should be scanned and watched. [`Handle`] then
//! asynchronously pushes file changes. Directory entries are configured in
//! free-form via list of globs, it's up to the [`Handle`] to interpret the globs
//! in any specific way.
//!
//! VFS stores a flat list of files. [`file_set::FileSet`] can partition this list
//! of files into disjoint sets of files. Traversal-like operations (including
//! getting the neighbor file by the relative path) are handled by the [`FileSet`].
//! [`FileSet`]s are also pushed to salsa and cause it to re-check `mod foo;`
//! declarations when files are created or deleted.
//!
//! [`FileSet`] and [`loader::Entry`] play similar, but different roles.
//! Both specify the "set of paths/files", one is geared towards file watching,
//! the other towards salsa changes. In particular, single [`FileSet`]
//! may correspond to several [`loader::Entry`]. For example, a crate from
//! crates.io which uses code generation would have two [`Entries`] -- for sources
//! in `~/.cargo`, and for generated code in `./target/debug/build`. It will
//! have a single [`FileSet`] which unions the two sources.
//!
//! [`set_file_contents`]: Vfs::set_file_contents
//! [`take_changes`]: Vfs::take_changes
//! [`FileSet`]: file_set::FileSet
//! [`Handle`]: loader::Handle
//! [`Entries`]: loader::Entry
mod anchored_path;
pub mod file_set;
pub mod loader;
mod path_interner;
mod vfs_path;

use std::{fmt, mem};

use crate::path_interner::PathInterner;

pub use crate::{
    anchored_path::{AnchoredPath, AnchoredPathBuf},
    vfs_path::VfsPath,
};
pub use paths::{AbsPath, AbsPathBuf};

/// Handle to a file in [`Vfs`]
///
/// Most functions in rust-analyzer use this when they need to refer to a file.
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct FileId(pub u32);

/// Storage for all files read by rust-analyzer.
///
/// For more informations see the [crate-level](crate) documentation.
#[derive(Default)]
pub struct Vfs {
    interner: PathInterner,
    data: Vec<Option<Vec<u8>>>,
    changes: Vec<ChangedFile>,
}

/// Changed file in the [`Vfs`].
pub struct ChangedFile {
    /// Id of the changed file
    pub file_id: FileId,
    /// Kind of change
    pub change_kind: ChangeKind,
}

impl ChangedFile {
    /// Returns `true` if the change is not [`Delete`](ChangeKind::Delete).
    pub fn exists(&self) -> bool {
        self.change_kind != ChangeKind::Delete
    }

    /// Returns `true` if the change is [`Create`](ChangeKind::Create) or
    /// [`Delete`](ChangeKind::Delete).
    pub fn is_created_or_deleted(&self) -> bool {
        matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete)
    }
}

/// Kind of [file change](ChangedFile).
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum ChangeKind {
    /// The file was (re-)created
    Create,
    /// The file was modified
    Modify,
    /// The file was deleted
    Delete,
}

impl Vfs {
    /// Amount of files currently stored.
    ///
    /// Note that this includes deleted files.
    pub fn len(&self) -> usize {
        self.data.len()
    }

    /// Id of the given path if it exists in the `Vfs` and is not deleted.
    pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
        self.interner.get(path).filter(|&it| self.get(it).is_some())
    }

    /// File path corresponding to the given `file_id`.
    ///
    /// # Panics
    ///
    /// Panics if the id is not present in the `Vfs`.
    pub fn file_path(&self, file_id: FileId) -> VfsPath {
        self.interner.lookup(file_id).clone()
    }

    /// File content corresponding to the given `file_id`.
    ///
    /// # Panics
    ///
    /// Panics if the id is not present in the `Vfs`, or if the corresponding file is
    /// deleted.
    pub fn file_contents(&self, file_id: FileId) -> &[u8] {
        self.get(file_id).as_deref().unwrap()
    }

    /// Returns an iterator over the stored ids and their corresponding paths.
    ///
    /// This will skip deleted files.
    pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
        (0..self.data.len())
            .map(|it| FileId(it as u32))
            .filter(move |&file_id| self.get(file_id).is_some())
            .map(move |file_id| {
                let path = self.interner.lookup(file_id);
                (file_id, path)
            })
    }

    /// Update the `path` with the given `contents`. `None` means the file was deleted.
    ///
    /// Returns `true` if the file was modified, and saves the [change](ChangedFile).
    ///
    /// If the path does not currently exists in the `Vfs`, allocates a new
    /// [`FileId`] for it.
    pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) -> bool {
        let file_id = self.alloc_file_id(path);
        let change_kind = match (&self.get(file_id), &contents) {
            (None, None) => return false,
            (None, Some(_)) => ChangeKind::Create,
            (Some(_), None) => ChangeKind::Delete,
            (Some(old), Some(new)) if old == new => return false,
            (Some(_), Some(_)) => ChangeKind::Modify,
        };

        *self.get_mut(file_id) = contents;
        self.changes.push(ChangedFile { file_id, change_kind });
        true
    }

    /// Returns `true` if the `Vfs` contains [changes](ChangedFile).
    pub fn has_changes(&self) -> bool {
        !self.changes.is_empty()
    }

    /// Drain and returns all the changes in the `Vfs`.
    pub fn take_changes(&mut self) -> Vec<ChangedFile> {
        mem::take(&mut self.changes)
    }

    /// Returns the id associated with `path`
    ///
    /// - If `path` does not exists in the `Vfs`, allocate a new id for it, associated with a
    /// deleted file;
    /// - Else, returns `path`'s id.
    ///
    /// Does not record a change.
    fn alloc_file_id(&mut self, path: VfsPath) -> FileId {
        let file_id = self.interner.intern(path);
        let idx = file_id.0 as usize;
        let len = self.data.len().max(idx + 1);
        self.data.resize_with(len, || None);
        file_id
    }

    /// Returns the content associated with the given `file_id`.
    ///
    /// # Panics
    ///
    /// Panics if no file is associated to that id.
    fn get(&self, file_id: FileId) -> &Option<Vec<u8>> {
        &self.data[file_id.0 as usize]
    }

    /// Mutably returns the content associated with the given `file_id`.
    ///
    /// # Panics
    ///
    /// Panics if no file is associated to that id.
    fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> {
        &mut self.data[file_id.0 as usize]
    }
}

impl fmt::Debug for Vfs {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Vfs").field("n_files", &self.data.len()).finish()
    }
}
<commit_msg>Chore: use short-circuiting for file ID getter<commit_after>//! # Virtual File System
//!
//! VFS stores all files read by rust-analyzer. Reading file contents from VFS
//! always returns the same contents, unless VFS was explicitly modified with
//! [`set_file_contents`]. All changes to VFS are logged, and can be retrieved via
//! [`take_changes`] method. The pack of changes is then pushed to `salsa` and
//! triggers incremental recomputation.
//!
//! Files in VFS are identified with [`FileId`]s -- interned paths. The notion of
//! the path, [`VfsPath`] is somewhat abstract: at the moment, it is represented
//! as an [`std::path::PathBuf`] internally, but this is an implementation detail.
//!
//! VFS doesn't do IO or file watching itself. For that, see the [`loader`]
//! module. [`loader::Handle`] is an object-safe trait which abstracts both file
//! loading and file watching. [`Handle`] is dynamically configured with a set of
//! directory entries which should be scanned and watched. [`Handle`] then
//! asynchronously pushes file changes. Directory entries are configured in
//! free-form via list of globs, it's up to the [`Handle`] to interpret the globs
//! in any specific way.
//!
//! VFS stores a flat list of files. [`file_set::FileSet`] can partition this list
//! of files into disjoint sets of files. Traversal-like operations (including
//! getting the neighbor file by the relative path) are handled by the [`FileSet`].
//! [`FileSet`]s are also pushed to salsa and cause it to re-check `mod foo;`
//! declarations when files are created or deleted.
//!
//! [`FileSet`] and [`loader::Entry`] play similar, but different roles.
//! Both specify the "set of paths/files", one is geared towards file watching,
//! the other towards salsa changes. In particular, single [`FileSet`]
//! may correspond to several [`loader::Entry`]. For example, a crate from
//! crates.io which uses code generation would have two [`Entries`] -- for sources
//! in `~/.cargo`, and for generated code in `./target/debug/build`. It will
//! have a single [`FileSet`] which unions the two sources.
//!
//! [`set_file_contents`]: Vfs::set_file_contents
//! [`take_changes`]: Vfs::take_changes
//! [`FileSet`]: file_set::FileSet
//! [`Handle`]: loader::Handle
//! [`Entries`]: loader::Entry
mod anchored_path;
pub mod file_set;
pub mod loader;
mod path_interner;
mod vfs_path;

use std::{fmt, mem};

use crate::path_interner::PathInterner;

pub use crate::{
    anchored_path::{AnchoredPath, AnchoredPathBuf},
    vfs_path::VfsPath,
};
pub use paths::{AbsPath, AbsPathBuf};

/// Handle to a file in [`Vfs`]
///
/// Most functions in rust-analyzer use this when they need to refer to a file.
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct FileId(pub u32);

/// Storage for all files read by rust-analyzer.
///
/// For more informations see the [crate-level](crate) documentation.
#[derive(Default)]
pub struct Vfs {
    interner: PathInterner,
    data: Vec<Option<Vec<u8>>>,
    changes: Vec<ChangedFile>,
}

/// Changed file in the [`Vfs`].
pub struct ChangedFile {
    /// Id of the changed file
    pub file_id: FileId,
    /// Kind of change
    pub change_kind: ChangeKind,
}

impl ChangedFile {
    /// Returns `true` if the change is not [`Delete`](ChangeKind::Delete).
    pub fn exists(&self) -> bool {
        self.change_kind != ChangeKind::Delete
    }

    /// Returns `true` if the change is [`Create`](ChangeKind::Create) or
    /// [`Delete`](ChangeKind::Delete).
    pub fn is_created_or_deleted(&self) -> bool {
        matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete)
    }
}

/// Kind of [file change](ChangedFile).
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum ChangeKind {
    /// The file was (re-)created
    Create,
    /// The file was modified
    Modify,
    /// The file was deleted
    Delete,
}

impl Vfs {
    /// Amount of files currently stored.
    ///
    /// Note that this includes deleted files.
    pub fn len(&self) -> usize {
        self.data.len()
    }

    /// Id of the given path if it exists in the `Vfs` and is not deleted.
    pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
        let it = self.interner.get(path)?;
        let _ = self.get(it).as_ref()?;
        Some(it)
    }

    /// File path corresponding to the given `file_id`.
    ///
    /// # Panics
    ///
    /// Panics if the id is not present in the `Vfs`.
    pub fn file_path(&self, file_id: FileId) -> VfsPath {
        self.interner.lookup(file_id).clone()
    }

    /// File content corresponding to the given `file_id`.
    ///
    /// # Panics
    ///
    /// Panics if the id is not present in the `Vfs`, or if the corresponding file is
    /// deleted.
    pub fn file_contents(&self, file_id: FileId) -> &[u8] {
        self.get(file_id).as_deref().unwrap()
    }

    /// Returns an iterator over the stored ids and their corresponding paths.
    ///
    /// This will skip deleted files.
    pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
        (0..self.data.len())
            .map(|it| FileId(it as u32))
            .filter(move |&file_id| self.get(file_id).is_some())
            .map(move |file_id| {
                let path = self.interner.lookup(file_id);
                (file_id, path)
            })
    }

    /// Update the `path` with the given `contents`. `None` means the file was deleted.
    ///
    /// Returns `true` if the file was modified, and saves the [change](ChangedFile).
    ///
    /// If the path does not currently exists in the `Vfs`, allocates a new
    /// [`FileId`] for it.
    pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) -> bool {
        let file_id = self.alloc_file_id(path);
        let change_kind = match (&self.get(file_id), &contents) {
            (None, None) => return false,
            (None, Some(_)) => ChangeKind::Create,
            (Some(_), None) => ChangeKind::Delete,
            (Some(old), Some(new)) if old == new => return false,
            (Some(_), Some(_)) => ChangeKind::Modify,
        };

        *self.get_mut(file_id) = contents;
        self.changes.push(ChangedFile { file_id, change_kind });
        true
    }

    /// Returns `true` if the `Vfs` contains [changes](ChangedFile).
    pub fn has_changes(&self) -> bool {
        !self.changes.is_empty()
    }

    /// Drain and returns all the changes in the `Vfs`.
    pub fn take_changes(&mut self) -> Vec<ChangedFile> {
        mem::take(&mut self.changes)
    }

    /// Returns the id associated with `path`
    ///
    /// - If `path` does not exists in the `Vfs`, allocate a new id for it, associated with a
    /// deleted file;
    /// - Else, returns `path`'s id.
    ///
    /// Does not record a change.
    fn alloc_file_id(&mut self, path: VfsPath) -> FileId {
        let file_id = self.interner.intern(path);
        let idx = file_id.0 as usize;
        let len = self.data.len().max(idx + 1);
        self.data.resize_with(len, || None);
        file_id
    }

    /// Returns the content associated with the given `file_id`.
    ///
    /// # Panics
    ///
    /// Panics if no file is associated to that id.
    fn get(&self, file_id: FileId) -> &Option<Vec<u8>> {
        &self.data[file_id.0 as usize]
    }

    /// Mutably returns the content associated with the given `file_id`.
    ///
    /// # Panics
    ///
    /// Panics if no file is associated to that id.
    fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> {
        &mut self.data[file_id.0 as usize]
    }
}

impl fmt::Debug for Vfs {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Vfs").field("n_files", &self.data.len()).finish()
    }
}
<|endoftext|>


<commit_before>use num::bigint::ToBigUint;
use num::Integer;

static BASE58_ALPHABET: &'static str = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

pub fn to_base58(data: &[u8]) -> String
{
    let mut n = 0u.to_biguint().unwrap();
    for (i, c) in data.iter().rev().enumerate() {
        let c = c.to_biguint().unwrap();

        n =n + (c << (i * 8));
    }

    let mut result = String::new();
    let limit = 58u.to_biguint().unwrap();
    while n >= limit {
        let (d, r) = n.div_rem(&limit);
        let r = r.to_uint().unwrap();

        n = d;
        result.push_char(BASE58_ALPHABET[r] as char);
    }
    let r = n.to_uint().unwrap();

    if r > 0 {
        result.push_char(BASE58_ALPHABET[r] as char);
    }

    for c in data.iter() {
        if *c == 0 {
            result.push_char(BASE58_ALPHABET[0] as char);
        } else {
            break;
        }
    }

    result.as_slice().chars().rev().collect()
}

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

    #[test]
    fn test_simple()
    {
        let tests = [
            ("hello world".as_bytes(), "StV1DL6CwTryKyV"),
            ("Hello World".as_bytes(), "JxF12TrwUP45BMd"),
        ];

        for &(s, n) in tests.iter() {
            let r = to_base58(s);

            assert_eq!(r.as_slice(), n);
        }
    }
}

#[cfg(test)]
mod bench
{
    use super::to_base58;
    use test::test::Bencher;

    #[bench]
    fn bench_simple(bh: &mut Bencher)
    {
        let s = "hello world";

        bh.iter(|| {
            to_base58(s.as_bytes());
        });
        bh.bytes = s.len() as u64;
    }
}
<commit_msg>Update, again.<commit_after>use num::bigint::ToBigUint;
use num::Integer;

static BASE58_ALPHABET: &'static str = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

pub fn to_base58(data: &[u8]) -> String
{
    let mut n = 0u.to_biguint().unwrap();
    for (i, c) in data.iter().rev().enumerate() {
        let c = c.to_biguint().unwrap();

        n =n + (c << (i * 8));
    }

    let mut result = String::new();
    let limit = 58u.to_biguint().unwrap();
    while n >= limit {
        let (d, r) = n.div_rem(&limit);
        let r = r.to_uint().unwrap();

        n = d;
        result.push_char(BASE58_ALPHABET.char_at(r));
    }
    let r = n.to_uint().unwrap();

    if r > 0 {
        result.push_char(BASE58_ALPHABET.char_at(r));
    }

    for c in data.iter() {
        if *c == 0 {
            result.push_char(BASE58_ALPHABET.char_at(0));
        } else {
            break;
        }
    }

    result.as_slice().chars().rev().collect()
}

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

    #[test]
    fn test_simple()
    {
        let tests = [
            ("hello world".as_bytes(), "StV1DL6CwTryKyV"),
            ("Hello World".as_bytes(), "JxF12TrwUP45BMd"),
        ];

        for &(s, n) in tests.iter() {
            let r = to_base58(s);

            assert_eq!(r.as_slice(), n);
        }
    }
}

#[cfg(test)]
mod bench
{
    use super::to_base58;
    use test::test::Bencher;

    #[bench]
    fn bench_simple(bh: &mut Bencher)
    {
        let s = "hello world";

        bh.iter(|| {
            to_base58(s.as_bytes());
        });
        bh.bytes = s.len() as u64;
    }
}
<|endoftext|>


<commit_before>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

use regex::Regex;
use toml::Value;

use store::Result;
use store::Header;
use error::StoreErrorKind as SEK;
use error::StoreError as SE;

#[cfg(feature = "early-panic")]
#[macro_export]
macro_rules! if_cfg_panic {
    ()                       => { panic!() };
    ($msg:expr)              => { panic!($msg) };
    ($fmt:expr, $($arg:tt)+) => { panic!($fmt, $($($arg),*)) };
}

#[cfg(not(feature = "early-panic"))]
#[macro_export]
macro_rules! if_cfg_panic {
    ()                       => { };
    ($msg:expr)              => { };
    ($fmt:expr, $($arg:tt)+) => { };
}

pub fn entry_buffer_to_header_content(buf: &str) -> Result<(Value, String)> {
    debug!("Building entry from string");
    lazy_static! {
        static ref RE: Regex = Regex::new(r"(?smx)
            ^---$
            (?P<header>.*) # Header
            ^---$\n
            (?P<content>.*) # Content
        ").unwrap();
    }

    let matches = match RE.captures(buf) {
        None    => return Err(SE::from_kind(SEK::MalformedEntry)),
        Some(s) => s,
    };

    let header = match matches.name("header") {
        None    => return Err(SE::from_kind(SEK::MalformedEntry)),
        Some(s) => s
    };

    let content = matches.name("content").map(|r| r.as_str()).unwrap_or("");

    Ok((Value::parse(header.as_str())?, String::from(content)))
}

<commit_msg>Fix syntax<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

use regex::Regex;
use toml::Value;

use store::Result;
use store::Header;
use error::StoreErrorKind as SEK;
use error::StoreError as SE;

#[cfg(feature = "early-panic")]
#[macro_export]
macro_rules! if_cfg_panic {
    ()                       => { panic!() };
    ($msg:expr)              => { panic!($msg) };
    ($fmt:expr, $($arg:tt)+) => { panic!($fmt, $($arg),+) };
}

#[cfg(not(feature = "early-panic"))]
#[macro_export]
macro_rules! if_cfg_panic {
    ()                       => { };
    ($msg:expr)              => { };
    ($fmt:expr, $($arg:tt)+) => { };
}

pub fn entry_buffer_to_header_content(buf: &str) -> Result<(Value, String)> {
    debug!("Building entry from string");
    lazy_static! {
        static ref RE: Regex = Regex::new(r"(?smx)
            ^---$
            (?P<header>.*) # Header
            ^---$\n
            (?P<content>.*) # Content
        ").unwrap();
    }

    let matches = match RE.captures(buf) {
        None    => return Err(SE::from_kind(SEK::MalformedEntry)),
        Some(s) => s,
    };

    let header = match matches.name("header") {
        None    => return Err(SE::from_kind(SEK::MalformedEntry)),
        Some(s) => s
    };

    let content = matches.name("content").map(|r| r.as_str()).unwrap_or("");

    Ok((Value::parse(header.as_str())?, String::from(content)))
}

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

extern crate libc;

use std::collections::HashMap;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};

use devicemapper::{DM, Device, DmNameBuf};

use super::super::engine::{Engine, Eventable, Pool};
use super::super::errors::{EngineError, EngineResult, ErrorEnum};
use super::super::structures::Table;
use super::super::types::{DevUuid, Name, PoolUuid, Redundancy, RenameAction};

use super::backstore::{find_all, is_stratis_device};
use super::cleanup::teardown_pools;
use super::devlinks;
use super::pool::StratPool;

pub const DEV_PATH: &str = "/dev/stratis";
const REQUIRED_DM_MINOR_VERSION: u32 = 37;

#[derive(Debug, PartialEq, Eq)]
pub enum DevOwnership {
    Ours(PoolUuid, DevUuid),
    Unowned,
    Theirs,
}

#[derive(Debug)]
pub struct StratEngine {
    pools: Table<StratPool>,

    // Map of stratis devices that have been found but one or more stratis block devices are missing
    // which prevents the associated pools from being setup.
    incomplete_pools: HashMap<PoolUuid, HashMap<Device, PathBuf>>,

    // Maps name of DM devices we are watching to the most recent event number
    // we've handled for each
    watched_dev_last_event_nrs: HashMap<DmNameBuf, u32>,
}

impl Eventable for DM {
    /// Get file we'd like to have monitored for activity
    fn get_pollable_fd(&mut self) -> RawFd {
        self.file().as_raw_fd()
    }

    fn clear_event(&mut self) -> EngineResult<()> {
        self.arm_poll()?;

        Ok(())
    }
}

impl StratEngine {
    /// Setup a StratEngine.
    /// 1. Verify the existence of Stratis /dev directory.
    /// 2. Setup all the pools belonging to the engine.
    ///    a. Places any devices which belong to a pool, but are not complete
    ///       in the incomplete pools data structure.
    ///
    /// Returns an error if the kernel doesn't support required DM features.
    /// Returns an error if there was an error reading device nodes.
    /// Returns an error and tears down all the pools if we find a pool with a
    /// duplicate name found.
    pub fn initialize() -> EngineResult<StratEngine> {
        let dm = DM::new()?;
        let minor_dm_version = dm.version()?.1;
        if minor_dm_version < REQUIRED_DM_MINOR_VERSION {
            let err_msg = format!("Requires DM minor version {} but kernel only supports {}",
                                  REQUIRED_DM_MINOR_VERSION,
                                  minor_dm_version);
            return Err(EngineError::Engine(ErrorEnum::Error, err_msg));
        }

        devlinks::setup_dev_path()?;

        let pools = find_all()?;

        let mut table = Table::default();
        let mut incomplete_pools = HashMap::new();

        for (pool_uuid, devices) in &pools {
            match StratPool::setup(&dm, *pool_uuid, devices) {
                Ok((pool_name, pool)) => {
                    let evicted = table.insert(pool_name, *pool_uuid, pool);
                    if evicted.is_some() {
                        // TODO: update state machine on failure.
                        let _ = teardown_pools(table);
                        let err_msg = "found two pools with the same name";
                        return Err(EngineError::Engine(ErrorEnum::Invalid, err_msg.into()));
                    }
                }
                Err(_) => {
                    // Unable to setup pool, it might become complete later.
                    incomplete_pools.insert(*pool_uuid, devices.clone());
                }
            }
        }

        let engine = StratEngine {
            pools: table,
            incomplete_pools,
            watched_dev_last_event_nrs: HashMap::new(),
        };

        devlinks::setup_devlinks(engine.pools().iter())?;

        Ok(engine)
    }

    /// Teardown Stratis, preparatory to a shutdown.
    pub fn teardown(self) -> EngineResult<()> {
        teardown_pools(self.pools)
    }
}

impl Engine for StratEngine {
    fn create_pool(&mut self,
                   name: &str,
                   blockdev_paths: &[&Path],
                   redundancy: Option<u16>,
                   force: bool)
                   -> EngineResult<PoolUuid> {

        let redundancy = calculate_redundancy!(redundancy);

        if self.pools.contains_name(name) {
            return Err(EngineError::Engine(ErrorEnum::AlreadyExists, name.into()));
        }

        let dm = DM::new()?;
        let (uuid, pool) = StratPool::initialize(&dm, name, blockdev_paths, redundancy, force)?;

        let name = Name::new(name.to_owned());
        devlinks::pool_added(&name)?;
        self.pools.insert(name, uuid, pool);
        Ok(uuid)
    }

    /// Evaluate a device node & devicemapper::Device to see if it's a valid
    /// stratis device.  If all the devices are present in the pool and the pool isn't already
    /// up and running, it will get setup and the pool uuid will be returned.
    ///
    /// Returns an error if the status of the block device can not be evaluated.
    fn block_evaluate(&mut self,
                      dev_node: PathBuf,
                      device: Device)
                      -> EngineResult<Option<PoolUuid>> {
        let dm = DM::new()?;
        if let Some(pool_uuid) = is_stratis_device(&dev_node)? {
            if self.pools.contains_uuid(pool_uuid) {
                // TODO: Handle the case where we have found a device for an already active pool
                // ref. https://github.com/stratis-storage/stratisd/issues/748
                warn!("udev add: pool {} is already known, ignoring device {:?}!",
                      pool_uuid,
                      dev_node);
                Ok(None)
            } else {

                let mut devices = self.incomplete_pools
                    .remove(&pool_uuid)
                    .or_else(|| Some(HashMap::new()))
                    .expect("We just retrieved or created a HashMap");
                devices.insert(device, dev_node);

                let rc = if let Ok((pool_name, pool)) =
                    StratPool::setup(&dm, pool_uuid, &devices) {
                    // We know we have a unique uuid, but the pools table requires a unique name too
                    // so we will ensure unique before we insert as we don't want to change the
                    // existing state if we have a conflict.
                    //
                    // We will also _not_ exit the daemon as we do in initialize as we were
                    // previously up and running for some duration of time.
                    if !self.pools.contains_name(&pool_name) {
                        self.pools.insert(pool_name, pool_uuid, pool);
                        Some(pool_uuid)
                    } else {
                        let dev_paths = devices
                            .values()
                            .map(|p| p.to_str().expect("Expecting valid device path!"))
                            .collect::<Vec<&str>>()
                            .join(", ");

                        self.incomplete_pools.insert(pool_uuid, devices);

                        error!("udev add: duplicate pool name {:?} for uuid {:?}, \
                                devices[{}], failing to setup complete pool!",
                               pool_name,
                               pool_uuid,
                               dev_paths);
                        if let Err(e) = pool.teardown() {
                            error!("Error while tearing down pool with duplicate name! {:?}!",
                                   e);
                        }
                        None
                    }
                } else {
                    self.incomplete_pools.insert(pool_uuid, devices);
                    None
                };
                Ok(rc)
            }
        } else {
            Ok(None)
        }
    }

    fn destroy_pool(&mut self, uuid: PoolUuid) -> EngineResult<bool> {
        if let Some((_, pool)) = self.pools.get_by_uuid(uuid) {
            if pool.has_filesystems() {
                return Err(EngineError::Engine(ErrorEnum::Busy,
                                               "filesystems remaining on pool".into()));
            };
        } else {
            return Ok(false);
        }

        let (pool_name, pool) =
            self.pools
                .remove_by_uuid(uuid)
                .expect("Must succeed since self.pools.get_by_uuid() returned a value");

        pool.destroy()?;
        devlinks::pool_removed(&pool_name)?;
        Ok(true)
    }

    fn rename_pool(&mut self, uuid: PoolUuid, new_name: &str) -> EngineResult<RenameAction> {
        let old_name = rename_pool_pre!(self; uuid; new_name);

        let (_, mut pool) =
            self.pools
                .remove_by_uuid(uuid)
                .expect("Must succeed since self.pools.get_by_uuid() returned a value");

        let new_name = Name::new(new_name.to_owned());
        if let Err(err) = pool.write_metadata(&new_name) {
            self.pools.insert(old_name, uuid, pool);
            Err(err)
        } else {
            self.pools.insert(new_name.clone(), uuid, pool);
            devlinks::pool_renamed(&old_name, &new_name)?;
            Ok(RenameAction::Renamed)
        }
    }

    fn get_pool(&self, uuid: PoolUuid) -> Option<(Name, &Pool)> {
        get_pool!(self; uuid)
    }

    fn get_mut_pool(&mut self, uuid: PoolUuid) -> Option<(Name, &mut Pool)> {
        get_mut_pool!(self; uuid)
    }

    fn configure_simulator(&mut self, _denominator: u32) -> EngineResult<()> {
        Ok(()) // we're not the simulator and not configurable, so just say ok
    }

    fn check(&mut self) -> () {
        check_engine!(self);
    }

    fn pools(&self) -> Vec<(Name, PoolUuid, &Pool)> {
        self.pools
            .iter()
            .map(|(name, uuid, pool)| (name.clone(), *uuid, pool as &Pool))
            .collect()
    }

    fn get_eventable(&mut self) -> EngineResult<Option<Box<Eventable>>> {
        Ok(Some(Box::new(DM::new()?)))
    }

    fn evented(&mut self) -> EngineResult<()> {
        let device_list: HashMap<_, _> = DM::new()?
            .list_devices()?
            .into_iter()
            .map(|(dm_name, _, event_nr)| {
                     (dm_name, event_nr.expect("Supported DM versions always provide a value"))
                 })
            .collect();

        for (pool_name, _, pool) in &mut self.pools {
            for dm_name in pool.get_eventing_dev_names() {
                if device_list.get(&dm_name) > self.watched_dev_last_event_nrs.get(&dm_name) {
                    pool.event_on(pool_name, &dm_name)?;
                }
            }
        }

        self.watched_dev_last_event_nrs = device_list;

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use super::super::tests::{loopbacked, real};

    use super::*;

    /// Verify that a pool rename causes the pool metadata to get the new name.
    fn test_pool_rename(paths: &[&Path]) {
        let mut engine = StratEngine::initialize().unwrap();

        let name1 = "name1";
        let uuid1 = engine.create_pool(&name1, paths, None, false).unwrap();

        let name2 = "name2";
        let action = engine.rename_pool(uuid1, name2).unwrap();

        assert_eq!(action, RenameAction::Renamed);
        engine.teardown().unwrap();

        let engine = StratEngine::initialize().unwrap();
        let pool_name: String = engine.get_pool(uuid1).unwrap().0.to_owned();
        assert_eq!(pool_name, name2);
    }

    #[test]
    pub fn loop_test_pool_rename() {
        loopbacked::test_with_spec(loopbacked::DeviceLimits::Range(1, 3), test_pool_rename);
    }

    #[test]
    pub fn real_test_pool_rename() {
        real::test_with_spec(real::DeviceLimits::AtLeast(1), test_pool_rename);
    }

    /// Test engine setup.
    /// 1. Create two pools.
    /// 2. Verify that both exist.
    /// 3. Teardown the engine.
    /// 4. Verify that pools are gone.
    /// 5. Initialize the engine.
    /// 6. Verify that pools can be found again.
    fn test_setup(paths: &[&Path]) {
        assert!(paths.len() > 1);

        let (paths1, paths2) = paths.split_at(paths.len() / 2);

        let mut engine = StratEngine::initialize().unwrap();

        let name1 = "name1";
        let uuid1 = engine.create_pool(&name1, paths1, None, false).unwrap();

        let name2 = "name2";
        let uuid2 = engine.create_pool(&name2, paths2, None, false).unwrap();

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();

        let engine = StratEngine::initialize().unwrap();

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());
    }

    #[test]
    pub fn loop_test_setup() {
        loopbacked::test_with_spec(loopbacked::DeviceLimits::Range(2, 3), test_setup);
    }

    #[test]
    pub fn real_test_setup() {
        real::test_with_spec(real::DeviceLimits::AtLeast(2), test_setup);
    }
}
<commit_msg>Extend basic engine initialization test<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/.

extern crate libc;

use std::collections::HashMap;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};

use devicemapper::{DM, Device, DmNameBuf};

use super::super::engine::{Engine, Eventable, Pool};
use super::super::errors::{EngineError, EngineResult, ErrorEnum};
use super::super::structures::Table;
use super::super::types::{DevUuid, Name, PoolUuid, Redundancy, RenameAction};

use super::backstore::{find_all, is_stratis_device};
use super::cleanup::teardown_pools;
use super::devlinks;
use super::pool::StratPool;

pub const DEV_PATH: &str = "/dev/stratis";
const REQUIRED_DM_MINOR_VERSION: u32 = 37;

#[derive(Debug, PartialEq, Eq)]
pub enum DevOwnership {
    Ours(PoolUuid, DevUuid),
    Unowned,
    Theirs,
}

#[derive(Debug)]
pub struct StratEngine {
    pools: Table<StratPool>,

    // Map of stratis devices that have been found but one or more stratis block devices are missing
    // which prevents the associated pools from being setup.
    incomplete_pools: HashMap<PoolUuid, HashMap<Device, PathBuf>>,

    // Maps name of DM devices we are watching to the most recent event number
    // we've handled for each
    watched_dev_last_event_nrs: HashMap<DmNameBuf, u32>,
}

impl Eventable for DM {
    /// Get file we'd like to have monitored for activity
    fn get_pollable_fd(&mut self) -> RawFd {
        self.file().as_raw_fd()
    }

    fn clear_event(&mut self) -> EngineResult<()> {
        self.arm_poll()?;

        Ok(())
    }
}

impl StratEngine {
    /// Setup a StratEngine.
    /// 1. Verify the existence of Stratis /dev directory.
    /// 2. Setup all the pools belonging to the engine.
    ///    a. Places any devices which belong to a pool, but are not complete
    ///       in the incomplete pools data structure.
    ///
    /// Returns an error if the kernel doesn't support required DM features.
    /// Returns an error if there was an error reading device nodes.
    /// Returns an error and tears down all the pools if we find a pool with a
    /// duplicate name found.
    pub fn initialize() -> EngineResult<StratEngine> {
        let dm = DM::new()?;
        let minor_dm_version = dm.version()?.1;
        if minor_dm_version < REQUIRED_DM_MINOR_VERSION {
            let err_msg = format!("Requires DM minor version {} but kernel only supports {}",
                                  REQUIRED_DM_MINOR_VERSION,
                                  minor_dm_version);
            return Err(EngineError::Engine(ErrorEnum::Error, err_msg));
        }

        devlinks::setup_dev_path()?;

        let pools = find_all()?;

        let mut table = Table::default();
        let mut incomplete_pools = HashMap::new();

        for (pool_uuid, devices) in &pools {
            match StratPool::setup(&dm, *pool_uuid, devices) {
                Ok((pool_name, pool)) => {
                    let evicted = table.insert(pool_name, *pool_uuid, pool);
                    if evicted.is_some() {
                        // TODO: update state machine on failure.
                        let _ = teardown_pools(table);
                        let err_msg = "found two pools with the same name";
                        return Err(EngineError::Engine(ErrorEnum::Invalid, err_msg.into()));
                    }
                }
                Err(_) => {
                    // Unable to setup pool, it might become complete later.
                    incomplete_pools.insert(*pool_uuid, devices.clone());
                }
            }
        }

        let engine = StratEngine {
            pools: table,
            incomplete_pools,
            watched_dev_last_event_nrs: HashMap::new(),
        };

        devlinks::setup_devlinks(engine.pools().iter())?;

        Ok(engine)
    }

    /// Teardown Stratis, preparatory to a shutdown.
    pub fn teardown(self) -> EngineResult<()> {
        teardown_pools(self.pools)
    }
}

impl Engine for StratEngine {
    fn create_pool(&mut self,
                   name: &str,
                   blockdev_paths: &[&Path],
                   redundancy: Option<u16>,
                   force: bool)
                   -> EngineResult<PoolUuid> {

        let redundancy = calculate_redundancy!(redundancy);

        if self.pools.contains_name(name) {
            return Err(EngineError::Engine(ErrorEnum::AlreadyExists, name.into()));
        }

        let dm = DM::new()?;
        let (uuid, pool) = StratPool::initialize(&dm, name, blockdev_paths, redundancy, force)?;

        let name = Name::new(name.to_owned());
        devlinks::pool_added(&name)?;
        self.pools.insert(name, uuid, pool);
        Ok(uuid)
    }

    /// Evaluate a device node & devicemapper::Device to see if it's a valid
    /// stratis device.  If all the devices are present in the pool and the pool isn't already
    /// up and running, it will get setup and the pool uuid will be returned.
    ///
    /// Returns an error if the status of the block device can not be evaluated.
    fn block_evaluate(&mut self,
                      dev_node: PathBuf,
                      device: Device)
                      -> EngineResult<Option<PoolUuid>> {
        let dm = DM::new()?;
        if let Some(pool_uuid) = is_stratis_device(&dev_node)? {
            if self.pools.contains_uuid(pool_uuid) {
                // TODO: Handle the case where we have found a device for an already active pool
                // ref. https://github.com/stratis-storage/stratisd/issues/748
                warn!("udev add: pool {} is already known, ignoring device {:?}!",
                      pool_uuid,
                      dev_node);
                Ok(None)
            } else {

                let mut devices = self.incomplete_pools
                    .remove(&pool_uuid)
                    .or_else(|| Some(HashMap::new()))
                    .expect("We just retrieved or created a HashMap");
                devices.insert(device, dev_node);

                let rc = if let Ok((pool_name, pool)) =
                    StratPool::setup(&dm, pool_uuid, &devices) {
                    // We know we have a unique uuid, but the pools table requires a unique name too
                    // so we will ensure unique before we insert as we don't want to change the
                    // existing state if we have a conflict.
                    //
                    // We will also _not_ exit the daemon as we do in initialize as we were
                    // previously up and running for some duration of time.
                    if !self.pools.contains_name(&pool_name) {
                        self.pools.insert(pool_name, pool_uuid, pool);
                        Some(pool_uuid)
                    } else {
                        let dev_paths = devices
                            .values()
                            .map(|p| p.to_str().expect("Expecting valid device path!"))
                            .collect::<Vec<&str>>()
                            .join(", ");

                        self.incomplete_pools.insert(pool_uuid, devices);

                        error!("udev add: duplicate pool name {:?} for uuid {:?}, \
                                devices[{}], failing to setup complete pool!",
                               pool_name,
                               pool_uuid,
                               dev_paths);
                        if let Err(e) = pool.teardown() {
                            error!("Error while tearing down pool with duplicate name! {:?}!",
                                   e);
                        }
                        None
                    }
                } else {
                    self.incomplete_pools.insert(pool_uuid, devices);
                    None
                };
                Ok(rc)
            }
        } else {
            Ok(None)
        }
    }

    fn destroy_pool(&mut self, uuid: PoolUuid) -> EngineResult<bool> {
        if let Some((_, pool)) = self.pools.get_by_uuid(uuid) {
            if pool.has_filesystems() {
                return Err(EngineError::Engine(ErrorEnum::Busy,
                                               "filesystems remaining on pool".into()));
            };
        } else {
            return Ok(false);
        }

        let (pool_name, pool) =
            self.pools
                .remove_by_uuid(uuid)
                .expect("Must succeed since self.pools.get_by_uuid() returned a value");

        pool.destroy()?;
        devlinks::pool_removed(&pool_name)?;
        Ok(true)
    }

    fn rename_pool(&mut self, uuid: PoolUuid, new_name: &str) -> EngineResult<RenameAction> {
        let old_name = rename_pool_pre!(self; uuid; new_name);

        let (_, mut pool) =
            self.pools
                .remove_by_uuid(uuid)
                .expect("Must succeed since self.pools.get_by_uuid() returned a value");

        let new_name = Name::new(new_name.to_owned());
        if let Err(err) = pool.write_metadata(&new_name) {
            self.pools.insert(old_name, uuid, pool);
            Err(err)
        } else {
            self.pools.insert(new_name.clone(), uuid, pool);
            devlinks::pool_renamed(&old_name, &new_name)?;
            Ok(RenameAction::Renamed)
        }
    }

    fn get_pool(&self, uuid: PoolUuid) -> Option<(Name, &Pool)> {
        get_pool!(self; uuid)
    }

    fn get_mut_pool(&mut self, uuid: PoolUuid) -> Option<(Name, &mut Pool)> {
        get_mut_pool!(self; uuid)
    }

    fn configure_simulator(&mut self, _denominator: u32) -> EngineResult<()> {
        Ok(()) // we're not the simulator and not configurable, so just say ok
    }

    fn check(&mut self) -> () {
        check_engine!(self);
    }

    fn pools(&self) -> Vec<(Name, PoolUuid, &Pool)> {
        self.pools
            .iter()
            .map(|(name, uuid, pool)| (name.clone(), *uuid, pool as &Pool))
            .collect()
    }

    fn get_eventable(&mut self) -> EngineResult<Option<Box<Eventable>>> {
        Ok(Some(Box::new(DM::new()?)))
    }

    fn evented(&mut self) -> EngineResult<()> {
        let device_list: HashMap<_, _> = DM::new()?
            .list_devices()?
            .into_iter()
            .map(|(dm_name, _, event_nr)| {
                     (dm_name, event_nr.expect("Supported DM versions always provide a value"))
                 })
            .collect();

        for (pool_name, _, pool) in &mut self.pools {
            for dm_name in pool.get_eventing_dev_names() {
                if device_list.get(&dm_name) > self.watched_dev_last_event_nrs.get(&dm_name) {
                    pool.event_on(pool_name, &dm_name)?;
                }
            }
        }

        self.watched_dev_last_event_nrs = device_list;

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use std::fs::remove_dir_all;

    use super::super::tests::{loopbacked, real};

    use super::*;

    /// Verify that a pool rename causes the pool metadata to get the new name.
    fn test_pool_rename(paths: &[&Path]) {
        let mut engine = StratEngine::initialize().unwrap();

        let name1 = "name1";
        let uuid1 = engine.create_pool(&name1, paths, None, false).unwrap();

        let name2 = "name2";
        let action = engine.rename_pool(uuid1, name2).unwrap();

        assert_eq!(action, RenameAction::Renamed);
        engine.teardown().unwrap();

        let engine = StratEngine::initialize().unwrap();
        let pool_name: String = engine.get_pool(uuid1).unwrap().0.to_owned();
        assert_eq!(pool_name, name2);
    }

    #[test]
    pub fn loop_test_pool_rename() {
        loopbacked::test_with_spec(loopbacked::DeviceLimits::Range(1, 3), test_pool_rename);
    }

    #[test]
    pub fn real_test_pool_rename() {
        real::test_with_spec(real::DeviceLimits::AtLeast(1), test_pool_rename);
    }

    /// Test engine setup.
    /// 1. Create two pools.
    /// 2. Verify that both exist.
    /// 3. Teardown the engine.
    /// 4. Initialize the engine.
    /// 5. Verify that pools can be found again.
    /// 6. Teardown the engine and remove "/dev/stratis".
    /// 7. Initialize the engine one more time.
    /// 8. Verify that both pools are found and that there are no incomplete pools.
    fn test_setup(paths: &[&Path]) {
        assert!(paths.len() > 1);

        let (paths1, paths2) = paths.split_at(paths.len() / 2);

        let mut engine = StratEngine::initialize().unwrap();

        let name1 = "name1";
        let uuid1 = engine.create_pool(&name1, paths1, None, false).unwrap();

        let name2 = "name2";
        let uuid2 = engine.create_pool(&name2, paths2, None, false).unwrap();

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();

        let engine = StratEngine::initialize().unwrap();

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();
        remove_dir_all(DEV_PATH).unwrap();

        let engine = StratEngine::initialize().unwrap();
        assert_eq!(engine.incomplete_pools, HashMap::new());

        assert!(engine.get_pool(uuid1).is_some());
        assert!(engine.get_pool(uuid2).is_some());

        engine.teardown().unwrap();
    }

    #[test]
    pub fn loop_test_setup() {
        loopbacked::test_with_spec(loopbacked::DeviceLimits::Range(2, 3), test_setup);
    }

    #[test]
    pub fn real_test_setup() {
        real::test_with_spec(real::DeviceLimits::AtLeast(2), test_setup);
    }
}
<|endoftext|>


<commit_before>// Impelmentation note: to allow unprivileged users to run it, this test makes
// use of user and mount namespaces. On systems that allow unprivileged user
// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run
// without root.

extern crate libc;
extern crate nix;
extern crate tempdir;

#[cfg(target_os = "linux")]
mod test_mount {
    use std::fs::{self, File};
    use std::io::{self, Read, Write};
    use std::os::unix::fs::OpenOptionsExt;
    use std::os::unix::fs::PermissionsExt;
    use std::process::{self, Command};

    use libc::{EACCES, EROFS};

    use nix::errno::Errno;
    use nix::mount::{mount, umount, MsFlags};
    use nix::sched::{unshare, CloneFlags};
    use nix::sys::stat::{self, Mode};
    use nix::unistd::getuid;

    use tempdir::TempDir;

    static SCRIPT_CONTENTS: &'static [u8] = b"#!/bin/sh
exit 23";

    const EXPECTED_STATUS: i32 = 23;

    const NONE: Option<&'static [u8]> = None;
    pub fn test_mount_tmpfs_without_flags_allows_rwx() {
        let tempdir = TempDir::new("nix-test_mount")
                          .unwrap_or_else(|e| panic!("tempdir failed: {}", e));

        mount(NONE,
              tempdir.path(),
              Some(b"tmpfs".as_ref()),
              MsFlags::empty(),
              NONE)
            .unwrap_or_else(|e| panic!("mount failed: {}", e));

        let test_path = tempdir.path().join("test");

        // Verify write.
        fs::OpenOptions::new()
            .create(true)
            .write(true)
            .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
            .open(&test_path)
            .or_else(|e|
                if Errno::from_i32(e.raw_os_error().unwrap()) == Errno::EOVERFLOW {
                    // Skip tests on certain Linux kernels which have a bug
                    // regarding tmpfs in namespaces.
                    // Ubuntu 14.04 and 16.04 are known to be affected; 16.10 is
                    // not.  There is no legitimate reason for open(2) to return
                    // EOVERFLOW here.
                    // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087
                    let stderr = io::stderr();
                    let mut handle = stderr.lock();
                    writeln!(handle, "Buggy Linux kernel detected.  Skipping test.")
                    .unwrap();
                    process::exit(0);
               } else {
                   panic!("open failed: {}", e);
               }
            )
            .and_then(|mut f| f.write(SCRIPT_CONTENTS))
            .unwrap_or_else(|e| panic!("write failed: {}", e));

        // Verify read.
        let mut buf = Vec::new();
        File::open(&test_path)
            .and_then(|mut f| f.read_to_end(&mut buf))
            .unwrap_or_else(|e| panic!("read failed: {}", e));
        assert_eq!(buf, SCRIPT_CONTENTS);

        // Verify execute.
        assert_eq!(EXPECTED_STATUS,
                   Command::new(&test_path)
                       .status()
                       .unwrap_or_else(|e| panic!("exec failed: {}", e))
                       .code()
                       .unwrap_or_else(|| panic!("child killed by signal")));

        umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
    }

    pub fn test_mount_rdonly_disallows_write() {
        let tempdir = TempDir::new("nix-test_mount")
                          .unwrap_or_else(|e| panic!("tempdir failed: {}", e));

        mount(NONE,
              tempdir.path(),
              Some(b"tmpfs".as_ref()),
              MsFlags::MS_RDONLY,
              NONE)
            .unwrap_or_else(|e| panic!("mount failed: {}", e));

        // EROFS: Read-only file system
        assert_eq!(EROFS as i32,
                   File::create(tempdir.path().join("test")).unwrap_err().raw_os_error().unwrap());

        umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
    }

    pub fn test_mount_noexec_disallows_exec() {
        let tempdir = TempDir::new("nix-test_mount")
                          .unwrap_or_else(|e| panic!("tempdir failed: {}", e));

        mount(NONE,
              tempdir.path(),
              Some(b"tmpfs".as_ref()),
              MsFlags::MS_NOEXEC,
              NONE)
            .unwrap_or_else(|e| panic!("mount failed: {}", e));

        let test_path = tempdir.path().join("test");

        fs::OpenOptions::new()
            .create(true)
            .write(true)
            .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
            .open(&test_path)
            .and_then(|mut f| f.write(SCRIPT_CONTENTS))
            .unwrap_or_else(|e| panic!("write failed: {}", e));

        // Verify that we cannot execute despite a+x permissions being set.
        let mode = stat::Mode::from_bits_truncate(fs::metadata(&test_path)
                                                      .map(|md| md.permissions().mode())
                                                      .unwrap_or_else(|e| {
                                                          panic!("metadata failed: {}", e)
                                                      }));

        assert!(mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
                "{:?} did not have execute permissions",
                &test_path);

        // EACCES: Permission denied
        assert_eq!(EACCES as i32,
                   Command::new(&test_path).status().unwrap_err().raw_os_error().unwrap());

        umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
    }

    pub fn test_mount_bind() {
        let tempdir = TempDir::new("nix-test_mount")
                          .unwrap_or_else(|e| panic!("tempdir failed: {}", e));
        let file_name = "test";

        {
            let mount_point = TempDir::new("nix-test_mount")
                                  .unwrap_or_else(|e| panic!("tempdir failed: {}", e));

            mount(Some(tempdir.path()),
                  mount_point.path(),
                  NONE,
                  MsFlags::MS_BIND,
                  NONE)
                .unwrap_or_else(|e| panic!("mount failed: {}", e));

            fs::OpenOptions::new()
                .create(true)
                .write(true)
                .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
                .open(mount_point.path().join(file_name))
                .and_then(|mut f| f.write(SCRIPT_CONTENTS))
                .unwrap_or_else(|e| panic!("write failed: {}", e));

            umount(mount_point.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
        }

        // Verify the file written in the mount shows up in source directory, even
        // after unmounting.

        let mut buf = Vec::new();
        File::open(tempdir.path().join(file_name))
            .and_then(|mut f| f.read_to_end(&mut buf))
            .unwrap_or_else(|e| panic!("read failed: {}", e));
        assert_eq!(buf, SCRIPT_CONTENTS);
    }

    pub fn setup_namespaces() {
        // Hold on to the uid in the parent namespace.
        let uid = getuid();

        unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| {
            let stderr = io::stderr();
            let mut handle = stderr.lock();
            writeln!(handle,
                     "unshare failed: {}. Are unprivileged user namespaces available?",
                     e).unwrap();
            writeln!(handle, "mount is not being tested").unwrap();
            // Exit with success because not all systems support unprivileged user namespaces, and
            // that's not what we're testing for.
            process::exit(0);
        });

        // Map user as uid 1000.
        fs::OpenOptions::new()
            .write(true)
            .open("/proc/self/uid_map")
            .and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes()))
            .unwrap_or_else(|e| panic!("could not write uid map: {}", e));
    }
}

// Test runner

/// Mimic normal test output (hackishly).
macro_rules! run_tests {
    ( $($test_fn:ident),* ) => {{
        print!("\n");

        $(
            print!("test test_mount::{} ... ", stringify!($test_fn));
            $test_fn();
            print!("ok\n");
        )*

        print!("\n");
    }}
}

#[cfg(target_os = "linux")]
fn main() {
    use test_mount::{setup_namespaces, test_mount_tmpfs_without_flags_allows_rwx,
                     test_mount_rdonly_disallows_write, test_mount_noexec_disallows_exec,
                     test_mount_bind};
    setup_namespaces();

    run_tests!(test_mount_tmpfs_without_flags_allows_rwx,
               test_mount_rdonly_disallows_write,
               test_mount_noexec_disallows_exec,
               test_mount_bind);
}

#[cfg(not(target_os = "linux"))]
fn main() {}
<commit_msg>Use println!() instead of print!(...n)<commit_after>// Impelmentation note: to allow unprivileged users to run it, this test makes
// use of user and mount namespaces. On systems that allow unprivileged user
// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run
// without root.

extern crate libc;
extern crate nix;
extern crate tempdir;

#[cfg(target_os = "linux")]
mod test_mount {
    use std::fs::{self, File};
    use std::io::{self, Read, Write};
    use std::os::unix::fs::OpenOptionsExt;
    use std::os::unix::fs::PermissionsExt;
    use std::process::{self, Command};

    use libc::{EACCES, EROFS};

    use nix::errno::Errno;
    use nix::mount::{mount, umount, MsFlags};
    use nix::sched::{unshare, CloneFlags};
    use nix::sys::stat::{self, Mode};
    use nix::unistd::getuid;

    use tempdir::TempDir;

    static SCRIPT_CONTENTS: &'static [u8] = b"#!/bin/sh
exit 23";

    const EXPECTED_STATUS: i32 = 23;

    const NONE: Option<&'static [u8]> = None;
    pub fn test_mount_tmpfs_without_flags_allows_rwx() {
        let tempdir = TempDir::new("nix-test_mount")
                          .unwrap_or_else(|e| panic!("tempdir failed: {}", e));

        mount(NONE,
              tempdir.path(),
              Some(b"tmpfs".as_ref()),
              MsFlags::empty(),
              NONE)
            .unwrap_or_else(|e| panic!("mount failed: {}", e));

        let test_path = tempdir.path().join("test");

        // Verify write.
        fs::OpenOptions::new()
            .create(true)
            .write(true)
            .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
            .open(&test_path)
            .or_else(|e|
                if Errno::from_i32(e.raw_os_error().unwrap()) == Errno::EOVERFLOW {
                    // Skip tests on certain Linux kernels which have a bug
                    // regarding tmpfs in namespaces.
                    // Ubuntu 14.04 and 16.04 are known to be affected; 16.10 is
                    // not.  There is no legitimate reason for open(2) to return
                    // EOVERFLOW here.
                    // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087
                    let stderr = io::stderr();
                    let mut handle = stderr.lock();
                    writeln!(handle, "Buggy Linux kernel detected.  Skipping test.")
                    .unwrap();
                    process::exit(0);
               } else {
                   panic!("open failed: {}", e);
               }
            )
            .and_then(|mut f| f.write(SCRIPT_CONTENTS))
            .unwrap_or_else(|e| panic!("write failed: {}", e));

        // Verify read.
        let mut buf = Vec::new();
        File::open(&test_path)
            .and_then(|mut f| f.read_to_end(&mut buf))
            .unwrap_or_else(|e| panic!("read failed: {}", e));
        assert_eq!(buf, SCRIPT_CONTENTS);

        // Verify execute.
        assert_eq!(EXPECTED_STATUS,
                   Command::new(&test_path)
                       .status()
                       .unwrap_or_else(|e| panic!("exec failed: {}", e))
                       .code()
                       .unwrap_or_else(|| panic!("child killed by signal")));

        umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
    }

    pub fn test_mount_rdonly_disallows_write() {
        let tempdir = TempDir::new("nix-test_mount")
                          .unwrap_or_else(|e| panic!("tempdir failed: {}", e));

        mount(NONE,
              tempdir.path(),
              Some(b"tmpfs".as_ref()),
              MsFlags::MS_RDONLY,
              NONE)
            .unwrap_or_else(|e| panic!("mount failed: {}", e));

        // EROFS: Read-only file system
        assert_eq!(EROFS as i32,
                   File::create(tempdir.path().join("test")).unwrap_err().raw_os_error().unwrap());

        umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
    }

    pub fn test_mount_noexec_disallows_exec() {
        let tempdir = TempDir::new("nix-test_mount")
                          .unwrap_or_else(|e| panic!("tempdir failed: {}", e));

        mount(NONE,
              tempdir.path(),
              Some(b"tmpfs".as_ref()),
              MsFlags::MS_NOEXEC,
              NONE)
            .unwrap_or_else(|e| panic!("mount failed: {}", e));

        let test_path = tempdir.path().join("test");

        fs::OpenOptions::new()
            .create(true)
            .write(true)
            .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
            .open(&test_path)
            .and_then(|mut f| f.write(SCRIPT_CONTENTS))
            .unwrap_or_else(|e| panic!("write failed: {}", e));

        // Verify that we cannot execute despite a+x permissions being set.
        let mode = stat::Mode::from_bits_truncate(fs::metadata(&test_path)
                                                      .map(|md| md.permissions().mode())
                                                      .unwrap_or_else(|e| {
                                                          panic!("metadata failed: {}", e)
                                                      }));

        assert!(mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
                "{:?} did not have execute permissions",
                &test_path);

        // EACCES: Permission denied
        assert_eq!(EACCES as i32,
                   Command::new(&test_path).status().unwrap_err().raw_os_error().unwrap());

        umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
    }

    pub fn test_mount_bind() {
        let tempdir = TempDir::new("nix-test_mount")
                          .unwrap_or_else(|e| panic!("tempdir failed: {}", e));
        let file_name = "test";

        {
            let mount_point = TempDir::new("nix-test_mount")
                                  .unwrap_or_else(|e| panic!("tempdir failed: {}", e));

            mount(Some(tempdir.path()),
                  mount_point.path(),
                  NONE,
                  MsFlags::MS_BIND,
                  NONE)
                .unwrap_or_else(|e| panic!("mount failed: {}", e));

            fs::OpenOptions::new()
                .create(true)
                .write(true)
                .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
                .open(mount_point.path().join(file_name))
                .and_then(|mut f| f.write(SCRIPT_CONTENTS))
                .unwrap_or_else(|e| panic!("write failed: {}", e));

            umount(mount_point.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
        }

        // Verify the file written in the mount shows up in source directory, even
        // after unmounting.

        let mut buf = Vec::new();
        File::open(tempdir.path().join(file_name))
            .and_then(|mut f| f.read_to_end(&mut buf))
            .unwrap_or_else(|e| panic!("read failed: {}", e));
        assert_eq!(buf, SCRIPT_CONTENTS);
    }

    pub fn setup_namespaces() {
        // Hold on to the uid in the parent namespace.
        let uid = getuid();

        unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| {
            let stderr = io::stderr();
            let mut handle = stderr.lock();
            writeln!(handle,
                     "unshare failed: {}. Are unprivileged user namespaces available?",
                     e).unwrap();
            writeln!(handle, "mount is not being tested").unwrap();
            // Exit with success because not all systems support unprivileged user namespaces, and
            // that's not what we're testing for.
            process::exit(0);
        });

        // Map user as uid 1000.
        fs::OpenOptions::new()
            .write(true)
            .open("/proc/self/uid_map")
            .and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes()))
            .unwrap_or_else(|e| panic!("could not write uid map: {}", e));
    }
}

// Test runner

/// Mimic normal test output (hackishly).
macro_rules! run_tests {
    ( $($test_fn:ident),* ) => {{
        println!();

        $(
            print!("test test_mount::{} ... ", stringify!($test_fn));
            $test_fn();
            println!("ok");
        )*

        println!();
    }}
}

#[cfg(target_os = "linux")]
fn main() {
    use test_mount::{setup_namespaces, test_mount_tmpfs_without_flags_allows_rwx,
                     test_mount_rdonly_disallows_write, test_mount_noexec_disallows_exec,
                     test_mount_bind};
    setup_namespaces();

    run_tests!(test_mount_tmpfs_without_flags_allows_rwx,
               test_mount_rdonly_disallows_write,
               test_mount_noexec_disallows_exec,
               test_mount_bind);
}

#[cfg(not(target_os = "linux"))]
fn main() {}
<|endoftext|>