<commit_before>extern crate regex;

use self::regex::{Error as RegexError, Regex};

use constant::ME;
use error::{CliError, CronError, CronErrorKind};
use lib::io::*;
use std::collections::BTreeSet;
use std::env;
use std::fmt::Display;
use std::io::Write;
use std::path::Path;
use std::process::{self, Stdio};
use std::str;

#[derive(Debug)]
pub struct Cron {
    bin    : String,
    upper  : String,
    my_area: String,
    lower  : String,
}

impl Cron {
    fn base_mark<D: Display>(keyword: &str, exec: D) -> String {
        format!(
            r#"###################################
# "{}" uses the lines {}.
# Please don't touch them and me!
###################################
{}"#,
            ME,
            keyword,
            exec,
        )
    }
    fn start_mark<S: AsRef<str>>(current_exe: S) -> String {
        Self::base_mark("from this", Self::patrol(current_exe))
    }
    fn end_mark() -> String {
        Self::base_mark("up to here", "")
    }

    fn patrol<S: AsRef<str>>(current_exe: S) -> String {
        format!("0 0 * * *\t{} patrol\n", current_exe.as_ref())
    }

    fn escape_asterisk<S: AsRef<str>>(s: S) -> String {
        let re = match Regex::new(r"\*") {
            Ok(re) => re,
            Err(_) => unreachable!("Mistake regular expression!!"),
        };
        re.replace_all(s.as_ref(), "\\*")
    }

    pub fn read() -> Result<Self, CliError> {
        let current_exe = try!(env::current_exe());
        let current_exe = try!(current_exe.to_str().ok_or(CronError::new(CronErrorKind::BinExistsInInvalidDir)));

        print_with_tag(Tag::Info, "Read cron");

        let result = process::Command::new("crontab")
            .arg("-l")
            .output();
        let output = try!(result);

        let contents = if output.status.success() {
            try!(str::from_utf8(&output.stdout).map_err(|_| CronError::new(CronErrorKind::InvalidCharacterCode)))
        } else {
            ""
        };

        let areas = format!(
            "^(?P<upper>(.|\n)*){}(?P<my_area>(.|\n)*){}(?P<lower>(.|\n)*)$",
            Self::escape_asterisk(Self::start_mark(current_exe)),
            Self::end_mark(),
        );
        let re = match Regex::new(&areas) {
            Ok(re) => re,
            Err(_) => unreachable!("Mistake the regular expression!!"),
        };

        let (upper, my_area, lower) = match re.captures(contents) {
            Some(caps) => match (caps.name("upper"), caps.name("my_area"), caps.name("lower")) {
                (Some(u), Some(m), Some(l)) => (u, m, l),
                _                           => unreachable!("Mistake regular expression!!"),
            },
            None => (contents, "", ""),
        };

        Ok(Cron {
            bin    : current_exe.to_string(),
            upper  : upper      .to_string(),
            my_area: my_area    .to_string(),
            lower  : lower      .to_string(),
        })
    }

    pub fn update(mut self, pairs: &[(&str, &str); 2]) -> Result<Self, CliError> {
        let current_dir = try!(Self::current_dir_string());
        self.delete(&current_dir);

        let my_new_area = pairs
            .iter()
            .fold(String::new(), |area, &(time, command)| {
                let line = format!("{}\tcd {} && {} {}\n", time, current_dir, self.bin, command);
                area + &line
            });

        self.my_area = self.my_area + &my_new_area;
        Ok(self)
    }

    fn re_for_matching_line<S: AsRef<str>>(&self, core: S) -> String {
        format!(r".*cd\s+{}\s+&&\s+{}.*\n", core.as_ref(), self.bin)
    }

    pub fn delete<S: AsRef<str>>(&mut self, dir: S) {
        let re = self.re_for_matching_line(dir);
        let re = match Regex::new(re.as_ref()) {
            Ok(re) => re,
            Err(_) => unreachable!("Wrong to use delete()!!"),
        };
        self.my_area = re.replace_all(&self.my_area, "");
    }

    pub fn discard(mut self) -> Result<Self, RegexError> {
        let re = self.re_for_matching_line(r"(?P<path>[^\s]+)");
        let target_paths = try!(Regex::new(re.as_ref()))
            .captures_iter(&self.my_area)
            .map(|caps| match caps.name("path") {
                Some(p) => p.to_string(),
                None    => unreachable!("Mistake regular expression!!"),
            })
            .filter(|path| !Path::new(path).is_dir())
            .collect::<BTreeSet<String>>();
        if target_paths.len() == 0 {
            return Ok(self);
        }

        let re_core = target_paths
            .iter()
            .fold(String::new(), |core, path| core + "|" + path); // We want to use true "Iterator.reduce()".
        let re_core = re_core.trim_left_matches('|');
        let re = self.re_for_matching_line(format!("({})", re_core));
        let re = try!(Regex::new(re.as_ref()));
        self.my_area = re.replace_all(&self.my_area, "");
        Ok(self)
    }

    pub fn set(self) -> Result<(), CliError> {
        print_with_tag(Tag::Info, "Set new cron");

        let result = process::Command::new("crontab")
            .stdin(Stdio::piped())
            .spawn();
        let mut child = try!(result);

        let contents = if self.my_area_is_empty() {
            self.upper + &self.lower
        } else {
            self.upper
                + &Self::start_mark(&self.bin)
                + &self.my_area
                + &Self::end_mark()
                + &self.lower
        };
        match &mut child.stdin {
            &mut Some(ref mut stdin) => try!(stdin.write_all(contents.as_bytes())),
            &mut None                => unreachable!("Please set Stdio::piped()!!"),
        };

        let output = try!(child.wait_with_output());
        if output.status.success() {
            Ok(())
        } else {
            Err(From::from(CronError::new(CronErrorKind::FailedToWrite)))
        }
    }

    pub fn current_dir_string() -> Result<String, CronError> {
        let current_dir = match env::current_dir() {
            Ok(d)  => d,
            Err(_) => unreachable!("There is a problem for current directory!!"),
        };
        current_dir
            .to_str()
            .ok_or(CronError::new(CronErrorKind::InvalidPath))
            .map(|s| s.to_string())
    }

    fn my_area_is_empty(&self) -> bool {
        let re = match Regex::new(r"^\s*$") {
            Ok(re) => re,
            Err(_) => unreachable!("Mistake the regular expression!!"),
        };
        re.is_match(self.my_area.as_ref())
    }
}

#[test]
fn my_area_is_empty_should_return_true() {
    let empties = [
        "",
        " ",
        "\t",
        "\r",
        "\n",
        " \t\r\n",
    ];

    for empty in &empties {
        let cron = Cron {
            bin    : ""   .to_string(),
            upper  : ""   .to_string(),
            my_area: empty.to_string(),
            lower  : ""   .to_string(),
        };
        assert!(cron.my_area_is_empty());
    }
}
#[test]
fn my_area_is_empty_should_return_false() {
    let non_empties = [
        "a",
        "ab",
        " ab",
        "a b",
        "ab ",
        "\tab",
        "a\tb",
        "ab\t",
        "\rab",
        "a\rb",
        "ab\r",
        "\nab",
        "a\nb",
        "ab\n",
        " \t\r\nab",
        "a \t\r\nb",
        "ab \t\r\n",
    ];

    for non_empty in &non_empties {
        let cron = Cron {
            bin    : ""       .to_string(),
            upper  : ""       .to_string(),
            my_area: non_empty.to_string(),
            lower  : ""       .to_string(),
        };
        assert!(!cron.my_area_is_empty());
    }
}

#[test]
fn delete_should_success() {
    fn wrap_by_extra_content<S: AsRef<str>>(target: S) -> String {
        let extra_content = "when cd path_to_extra && path_to_bin command\n";
        format!("{}{}{}", extra_content, target.as_ref(), extra_content)
    }

    let mut cron = Cron {
        bin    : "path_to_bin".to_string(),
        upper  : "upper"      .to_string(),
        my_area: "middle"     .to_string(),
        lower  : "lower"      .to_string(),
    };

    let dir = "path_to_dir";

    let areas = vec![
        ("".to_string(), format!("cd {} && {}\n"             , dir, &cron.bin)),
        ("".to_string(), format!("when cd {} && {}\n"        , dir, &cron.bin)),
        ("".to_string(), format!("cd {} && {} command\n"     , dir, &cron.bin)),
        ("".to_string(), format!("when cd {} && {} command\n", dir, &cron.bin)),

        (wrap_by_extra_content(""), wrap_by_extra_content(format!("cd {} && {}\n"             , dir, &cron.bin))),
        (wrap_by_extra_content(""), wrap_by_extra_content(format!("when cd {} && {}\n"        , dir, &cron.bin))),
        (wrap_by_extra_content(""), wrap_by_extra_content(format!("cd {} && {} command\n"     , dir, &cron.bin))),
        (wrap_by_extra_content(""), wrap_by_extra_content(format!("when cd {} && {} command\n", dir, &cron.bin))),
    ];

    for (correct, area) in areas.into_iter() {
        cron.my_area = area;
        cron.delete(&dir);
        assert_eq!(correct, cron.my_area);
    }
}
#[test]
#[should_panic(expect = "entered unreachable code")]
fn delete_should_panic() {
    let mut cron = Cron {
        bin    : "path_to_bin".to_string(),
        upper  : "upper"      .to_string(),
        my_area: "middle"     .to_string(),
        lower  : "lower"      .to_string(),
    };

    cron.delete("() <= mistaken Regular Expression!");
}

#[test]
fn discard_should_remove_lines_including_non_existing_dir() {
    let mut cron = Cron {
        bin    : "path_to_bin".to_string(),
        upper  : "upper"      .to_string(),
        my_area: "middle"     .to_string(),
        lower  : "lower"      .to_string(),
    };

    cron.my_area = format!("
        cd /path/to/non/existing/dir && {} command
        cd /path/to/non/existing/dir && {} command2
        cd /path/to/really/non/existing/dir && {} command
        cd /path/to/really/non/existing/dir && {} command2
    ", &cron.bin, &cron.bin, &cron.bin, &cron.bin);

    let cron = cron.discard();
    assert!(cron.is_ok());
    assert!(cron.unwrap().my_area_is_empty());
}
#[test]
fn discard_should_return_regex_err() {
    let mut cron = Cron {
        bin    : "path_to_bin".to_string(),
        upper  : "upper"      .to_string(),
        my_area: "middle"     .to_string(),
        lower  : "lower"      .to_string(),
    };

    cron.my_area = format!("
        cd /()/<=/mischief/by/bad/user && {} command
    ", &cron.bin);

    let cron = cron.discard();
    assert!(cron.is_err());
}
<commit_msg>[Remedy] Cron::discard() delete lines including dir which lost ".kic"<commit_after>extern crate regex;

use self::regex::{Error as RegexError, Regex};

use constant::{ME, WORKING_DIR_NAME};
use error::{CliError, CronError, CronErrorKind};
use lib::io::*;
use std::collections::BTreeSet;
use std::env;
use std::fmt::Display;
use std::io::Write;
use std::path::Path;
use std::process::{self, Stdio};
use std::str;

#[derive(Debug)]
pub struct Cron {
    bin    : String,
    upper  : String,
    my_area: String,
    lower  : String,
}

impl Cron {
    fn base_mark<D: Display>(keyword: &str, exec: D) -> String {
        format!(
            r#"###################################
# "{}" uses the lines {}.
# Please don't touch them and me!
###################################
{}"#,
            ME,
            keyword,
            exec,
        )
    }
    fn start_mark<S: AsRef<str>>(current_exe: S) -> String {
        Self::base_mark("from this", Self::patrol(current_exe))
    }
    fn end_mark() -> String {
        Self::base_mark("up to here", "")
    }

    fn patrol<S: AsRef<str>>(current_exe: S) -> String {
        format!("0 0 * * *\t{} patrol\n", current_exe.as_ref())
    }

    fn escape_asterisk<S: AsRef<str>>(s: S) -> String {
        let re = match Regex::new(r"\*") {
            Ok(re) => re,
            Err(_) => unreachable!("Mistake regular expression!!"),
        };
        re.replace_all(s.as_ref(), "\\*")
    }

    pub fn read() -> Result<Self, CliError> {
        let current_exe = try!(env::current_exe());
        let current_exe = try!(current_exe.to_str().ok_or(CronError::new(CronErrorKind::BinExistsInInvalidDir)));

        print_with_tag(Tag::Info, "Read cron");

        let result = process::Command::new("crontab")
            .arg("-l")
            .output();
        let output = try!(result);

        let contents = if output.status.success() {
            try!(str::from_utf8(&output.stdout).map_err(|_| CronError::new(CronErrorKind::InvalidCharacterCode)))
        } else {
            ""
        };

        let areas = format!(
            "^(?P<upper>(.|\n)*){}(?P<my_area>(.|\n)*){}(?P<lower>(.|\n)*)$",
            Self::escape_asterisk(Self::start_mark(current_exe)),
            Self::end_mark(),
        );
        let re = match Regex::new(&areas) {
            Ok(re) => re,
            Err(_) => unreachable!("Mistake the regular expression!!"),
        };

        let (upper, my_area, lower) = match re.captures(contents) {
            Some(caps) => match (caps.name("upper"), caps.name("my_area"), caps.name("lower")) {
                (Some(u), Some(m), Some(l)) => (u, m, l),
                _                           => unreachable!("Mistake regular expression!!"),
            },
            None => (contents, "", ""),
        };

        Ok(Cron {
            bin    : current_exe.to_string(),
            upper  : upper      .to_string(),
            my_area: my_area    .to_string(),
            lower  : lower      .to_string(),
        })
    }

    pub fn update(mut self, pairs: &[(&str, &str); 2]) -> Result<Self, CliError> {
        let current_dir = try!(Self::current_dir_string());
        self.delete(&current_dir);

        let my_new_area = pairs
            .iter()
            .fold(String::new(), |area, &(time, command)| {
                let line = format!("{}\tcd {} && {} {}\n", time, current_dir, self.bin, command);
                area + &line
            });

        self.my_area = self.my_area + &my_new_area;
        Ok(self)
    }

    fn re_for_matching_missing_dir_line<S: AsRef<str>>(&self, core: S) -> String {
        format!(r".*cd\s+{}\s+&&\s+{}.*\n", core.as_ref(), self.bin)
    }

    pub fn delete<S: AsRef<str>>(&mut self, dir: S) {
        let re = self.re_for_matching_missing_dir_line(dir);
        let re = match Regex::new(re.as_ref()) {
            Ok(re) => re,
            Err(_) => unreachable!("Wrong to use delete()!!"),
        };
        self.my_area = re.replace_all(&self.my_area, "");
    }

    pub fn discard(mut self) -> Result<Self, RegexError> {
        let re = self.re_for_matching_missing_dir_line(r"(?P<path>[^\s]+)");
        let re = match Regex::new(re.as_ref()) {
            Ok(re) => re,
            Err(_) => unreachable!("Mistake regular expression!!"),
        };
        let target_paths = re
            .captures_iter(&self.my_area)
            .map(|caps| match caps.name("path") {
                Some(p) => p.to_string(),
                None    => unreachable!("Mistake keyword for capturing!!"),
            })
            .filter(|path| !Path::new(path).join(WORKING_DIR_NAME).is_dir())
            .collect::<BTreeSet<String>>();

        if target_paths.len() != 0 {
            let re_core = target_paths
                .iter()
                .fold(String::new(), |core, path| core + "|" + path); // We want to use true "Iterator.reduce()".
            let re_core = format!("({})", re_core.trim_left_matches('|'));
            let re = self.re_for_matching_missing_dir_line(re_core);
            let re = try!(Regex::new(re.as_ref()));
            self.my_area = re.replace_all(&self.my_area, "");
        }

        Ok(self)
    }

    pub fn set(self) -> Result<(), CliError> {
        print_with_tag(Tag::Info, "Set new cron");

        let result = process::Command::new("crontab")
            .stdin(Stdio::piped())
            .spawn();
        let mut child = try!(result);

        let contents = if self.my_area_is_empty() {
            self.upper + &self.lower
        } else {
            self.upper
                + &Self::start_mark(&self.bin)
                + &self.my_area
                + &Self::end_mark()
                + &self.lower
        };
        match &mut child.stdin {
            &mut Some(ref mut stdin) => try!(stdin.write_all(contents.as_bytes())),
            &mut None                => unreachable!("Please set Stdio::piped()!!"),
        };

        let output = try!(child.wait_with_output());
        if output.status.success() {
            Ok(())
        } else {
            Err(From::from(CronError::new(CronErrorKind::FailedToWrite)))
        }
    }

    pub fn current_dir_string() -> Result<String, CronError> {
        let current_dir = match env::current_dir() {
            Ok(d)  => d,
            Err(_) => unreachable!("There is a problem for current directory!!"),
        };
        current_dir
            .to_str()
            .ok_or(CronError::new(CronErrorKind::InvalidPath))
            .map(|s| s.to_string())
    }

    fn my_area_is_empty(&self) -> bool {
        let re = match Regex::new(r"^\s*$") {
            Ok(re) => re,
            Err(_) => unreachable!("Mistake the regular expression!!"),
        };
        re.is_match(self.my_area.as_ref())
    }
}

#[test]
fn my_area_is_empty_should_return_true() {
    let empties = [
        "",
        " ",
        "\t",
        "\r",
        "\n",
        " \t\r\n",
    ];

    for empty in &empties {
        let cron = Cron {
            bin    : ""   .to_string(),
            upper  : ""   .to_string(),
            my_area: empty.to_string(),
            lower  : ""   .to_string(),
        };
        assert!(cron.my_area_is_empty());
    }
}
#[test]
fn my_area_is_empty_should_return_false() {
    let non_empties = [
        "a",
        "ab",
        " ab",
        "a b",
        "ab ",
        "\tab",
        "a\tb",
        "ab\t",
        "\rab",
        "a\rb",
        "ab\r",
        "\nab",
        "a\nb",
        "ab\n",
        " \t\r\nab",
        "a \t\r\nb",
        "ab \t\r\n",
    ];

    for non_empty in &non_empties {
        let cron = Cron {
            bin    : ""       .to_string(),
            upper  : ""       .to_string(),
            my_area: non_empty.to_string(),
            lower  : ""       .to_string(),
        };
        assert!(!cron.my_area_is_empty());
    }
}

#[test]
fn delete_should_success() {
    fn wrap_by_extra_content<S: AsRef<str>>(target: S) -> String {
        let extra_content = "when cd path_to_extra && path_to_bin command\n";
        format!("{}{}{}", extra_content, target.as_ref(), extra_content)
    }

    let mut cron = Cron {
        bin    : "path_to_bin".to_string(),
        upper  : "upper"      .to_string(),
        my_area: "middle"     .to_string(),
        lower  : "lower"      .to_string(),
    };

    let dir = "path_to_dir";

    let areas = vec![
        ("".to_string(), format!("cd {} && {}\n"             , dir, &cron.bin)),
        ("".to_string(), format!("when cd {} && {}\n"        , dir, &cron.bin)),
        ("".to_string(), format!("cd {} && {} command\n"     , dir, &cron.bin)),
        ("".to_string(), format!("when cd {} && {} command\n", dir, &cron.bin)),

        (wrap_by_extra_content(""), wrap_by_extra_content(format!("cd {} && {}\n"             , dir, &cron.bin))),
        (wrap_by_extra_content(""), wrap_by_extra_content(format!("when cd {} && {}\n"        , dir, &cron.bin))),
        (wrap_by_extra_content(""), wrap_by_extra_content(format!("cd {} && {} command\n"     , dir, &cron.bin))),
        (wrap_by_extra_content(""), wrap_by_extra_content(format!("when cd {} && {} command\n", dir, &cron.bin))),
    ];

    for (correct, area) in areas.into_iter() {
        cron.my_area = area;
        cron.delete(&dir);
        assert_eq!(correct, cron.my_area);
    }
}
#[test]
#[should_panic(expect = "entered unreachable code")]
fn delete_should_panic() {
    let mut cron = Cron {
        bin    : "path_to_bin".to_string(),
        upper  : "upper"      .to_string(),
        my_area: "middle"     .to_string(),
        lower  : "lower"      .to_string(),
    };

    cron.delete("() <= mistaken Regular Expression!");
}

#[test]
fn discard_should_remove_lines_including_non_existing_dir() {
    let mut cron = Cron {
        bin    : "path_to_bin".to_string(),
        upper  : "upper"      .to_string(),
        my_area: "middle"     .to_string(),
        lower  : "lower"      .to_string(),
    };

    cron.my_area = format!("
        cd /bin && {} command
        cd /bin && {} command2
        cd /path/to/non/existing/dir && {} command
        cd /path/to/non/existing/dir && {} command2
        cd /path/to/really/non/existing/dir && {} command
        cd /path/to/really/non/existing/dir && {} command2
    ", &cron.bin, &cron.bin, &cron.bin, &cron.bin, &cron.bin, &cron.bin);

    let cron = cron.discard();
    assert!(cron.is_ok());
    assert!(cron.unwrap().my_area_is_empty());
}
#[test]
fn discard_should_return_regex_err() {
    let mut cron = Cron {
        bin    : "path_to_bin".to_string(),
        upper  : "upper"      .to_string(),
        my_area: "middle"     .to_string(),
        lower  : "lower"      .to_string(),
    };

    cron.my_area = format!("
        cd /()/<=/mischief/by/bad/user && {} command
    ", &cron.bin);

    let cron = cron.discard();
    assert!(cron.is_err());
}
<|endoftext|>


<commit_before>//! # time_summary
//!
//! Prints out the time summary for the flow.
//!

use crate::types::{CliArgs, Config};
use envmnt;
use std::time::SystemTime;

pub(crate) fn add(time_summary: &mut Vec<(String, u128)>, name: &str, start_time: SystemTime) {
    match start_time.elapsed() {
        Ok(elapsed) => time_summary.push((name.to_string(), elapsed.as_millis())),
        _ => (),
    };
}

pub(crate) fn print(time_summary: &Vec<(String, u128)>) {
    if envmnt::is("CARGO_MAKE_PRINT_TIME_SUMMARY") {
        let mut total_time = 0;
        let mut max_name_size = 0;
        for entry in time_summary {
            total_time = total_time + entry.1;
            let name_size = entry.0.len();
            if max_name_size < name_size {
                max_name_size = name_size;
            }
        }

        info!("================Time Summary================");
        for entry in time_summary {
            let percentage = (entry.1 as f64 / total_time as f64) * 100.0;
            let seconds = entry.1 as f64 / 1000.0;
            let name_size = entry.0.len();
            let gap_size = max_name_size - name_size + 3;
            let gap = format!("{: <1$}", "", gap_size);
            info!(
                "{}:{}{:.2}%\t   {:.2} seconds",
                entry.0, gap, percentage, seconds
            );
        }
        info!("============================================");
    }
}

pub(crate) fn init(config: &Config, cli_args: &CliArgs) {
    if config.config.time_summary.unwrap_or(false)
        || cli_args.print_time_summary
        || envmnt::is("CARGO_MAKE_CI")
    {
        envmnt::set_bool("CARGO_MAKE_PRINT_TIME_SUMMARY", true);
    }
}
<commit_msg>sort time summary #446<commit_after>//! # time_summary
//!
//! Prints out the time summary for the flow.
//!

use crate::types::{CliArgs, Config};
use envmnt;
use std::cmp::Ordering;
use std::time::SystemTime;

pub(crate) fn add(time_summary: &mut Vec<(String, u128)>, name: &str, start_time: SystemTime) {
    match start_time.elapsed() {
        Ok(elapsed) => time_summary.push((name.to_string(), elapsed.as_millis())),
        _ => (),
    };
}

pub(crate) fn print(time_summary: &Vec<(String, u128)>) {
    if envmnt::is("CARGO_MAKE_PRINT_TIME_SUMMARY") {
        let mut time_summary_sorted = time_summary.clone();
        time_summary_sorted
            .sort_by(|entry1, entry2| entry2.1.partial_cmp(&entry1.1).unwrap_or(Ordering::Equal));

        let mut total_time = 0;
        let mut max_name_size = 0;
        for entry in &time_summary_sorted {
            total_time = total_time + entry.1;
            let name_size = entry.0.len();
            if max_name_size < name_size {
                max_name_size = name_size;
            }
        }

        info!("================Time Summary================");
        for entry in &time_summary_sorted {
            let percentage = (entry.1 as f64 / total_time as f64) * 100.0;
            let seconds = entry.1 as f64 / 1000.0;
            let name_size = entry.0.len();
            let gap_size = max_name_size - name_size + 3;
            let gap = format!("{: <1$}", "", gap_size);
            info!(
                "{}:{}{:.2}%\t   {:.2} seconds",
                entry.0, gap, percentage, seconds
            );
        }
        info!("============================================");
    }
}

pub(crate) fn init(config: &Config, cli_args: &CliArgs) {
    if config.config.time_summary.unwrap_or(false)
        || cli_args.print_time_summary
        || envmnt::is("CARGO_MAKE_CI")
    {
        envmnt::set_bool("CARGO_MAKE_PRINT_TIME_SUMMARY", true);
    }
}
<|endoftext|>


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

// rustpkg unit tests

use context::Ctx;
use core::hashmap::HashMap;
use core::path::Path;
use std::tempfile::mkdtemp;
use util::{PkgId, default_version};
use path_util::{target_executable_in_workspace, target_library_in_workspace,
               target_test_in_workspace, target_bench_in_workspace,
               make_dir_rwx, u_rwx, RemotePath, LocalPath, normalize,
               built_bench_in_workspace, built_test_in_workspace};

fn fake_ctxt(sysroot_opt: Option<@Path>) -> Ctx {
    Ctx {
        sysroot_opt: sysroot_opt,
        json: false,
        dep_cache: @mut HashMap::new()
    }
}

fn fake_pkg() -> PkgId {
    let sn = ~"bogus";
    let remote = RemotePath(Path(sn));
    PkgId {
        local_path: normalize(copy remote),
        remote_path: remote,
        short_name: sn,
        version: default_version()
    }
}

fn remote_pkg() -> PkgId {
    let remote = RemotePath(Path(~"github.com/catamorphism/test-pkg"));
    PkgId {
        local_path: normalize(copy remote),
        remote_path: remote,
        short_name: ~"test_pkg",
        version: default_version()
    }
}

fn writeFile(file_path: &Path, contents: ~str) {
    let out: @io::Writer =
        result::get(&io::file_writer(file_path,
                                     ~[io::Create, io::Truncate]));
    out.write_line(contents);
}

fn mk_temp_workspace(short_name: &LocalPath) -> Path {
    let workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
    // include version number in directory name
    let package_dir = workspace.push(~"src").push(fmt!("%s-0.1", short_name.to_str()));
    assert!(os::mkdir_recursive(&package_dir, u_rwx));
    // Create main, lib, test, and bench files
    writeFile(&package_dir.push(~"main.rs"),
              ~"fn main() { let _x = (); }");
    writeFile(&package_dir.push(~"lib.rs"),
              ~"pub fn f() { let _x = (); }");
    writeFile(&package_dir.push(~"test.rs"),
              ~"#[test] pub fn f() { (); }");
    writeFile(&package_dir.push(~"bench.rs"),
              ~"#[bench] pub fn f() { (); }");
    workspace
}

fn is_rwx(p: &Path) -> bool {
    use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR};

    match p.get_mode() {
        None => return false,
        Some(m) =>
            ((m & S_IRUSR as uint) == S_IRUSR as uint
            && (m & S_IWUSR as uint) == S_IWUSR as uint
            && (m & S_IXUSR as uint) == S_IXUSR as uint)
    }
}

fn test_sysroot() -> Path {
    // Totally gross hack but it's just for test cases.
    // Infer the sysroot from the exe name and pray that it's right.
    // (Did I mention it was a gross hack?)
    let self_path = os::self_exe_path().expect("Couldn't get self_exe path");
    self_path.pop()
}

#[test]
fn test_make_dir_rwx() {
    let temp = &os::tmpdir();
    let dir = temp.push(~"quux");
    assert!(!os::path_exists(&dir) ||
            os::remove_dir_recursive(&dir));
    debug!("Trying to make %s", dir.to_str());
    assert!(make_dir_rwx(&dir));
    assert!(os::path_is_dir(&dir));
    assert!(is_rwx(&dir));
    assert!(os::remove_dir_recursive(&dir));
}

#[test]
fn test_install_valid() {
    let sysroot = test_sysroot();
    debug!("sysroot = %s", sysroot.to_str());
    let ctxt = fake_ctxt(Some(@sysroot));
    let temp_pkg_id = fake_pkg();
    let temp_workspace = mk_temp_workspace(&temp_pkg_id.local_path);
    // should have test, bench, lib, and main
    ctxt.install(&temp_workspace, &temp_pkg_id);
    // Check that all files exist
    let exec = target_executable_in_workspace(&temp_pkg_id, &temp_workspace);
    debug!("exec = %s", exec.to_str());
    assert!(os::path_exists(&exec));
    assert!(is_rwx(&exec));
    let lib = target_library_in_workspace(&temp_pkg_id, &temp_workspace);
    debug!("lib = %s", lib.to_str());
    assert!(os::path_exists(&lib));
    assert!(is_rwx(&lib));
    // And that the test and bench executables aren't installed
    assert!(!os::path_exists(&target_test_in_workspace(&temp_pkg_id, &temp_workspace)));
    let bench = target_bench_in_workspace(&temp_pkg_id, &temp_workspace);
    debug!("bench = %s", bench.to_str());
    assert!(!os::path_exists(&bench));
}

#[test]
fn test_install_invalid() {
    use conditions::nonexistent_package::cond;
    use cond1 = conditions::missing_pkg_files::cond;

    let ctxt = fake_ctxt(None);
    let pkgid = fake_pkg();
    let temp_workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
    let mut error_occurred = false;
    let mut error1_occurred = false;
    do cond1.trap(|_| {
        error1_occurred = true;
    }).in {
        do cond.trap(|_| {
            error_occurred = true;
        }).in {
            ctxt.install(&temp_workspace, &pkgid);
        }
    }
    assert!(error_occurred && error1_occurred);
}

#[test]
fn test_install_url() {
    let workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
    let sysroot = test_sysroot();
    debug!("sysroot = %s", sysroot.to_str());
    let ctxt = fake_ctxt(Some(@sysroot));
    let temp_pkg_id = remote_pkg();
    // should have test, bench, lib, and main
    ctxt.install(&workspace, &temp_pkg_id);
    // Check that all files exist
    let exec = target_executable_in_workspace(&temp_pkg_id, &workspace);
    debug!("exec = %s", exec.to_str());
    assert!(os::path_exists(&exec));
    assert!(is_rwx(&exec));
    let lib = target_library_in_workspace(&temp_pkg_id, &workspace);
    debug!("lib = %s", lib.to_str());
    assert!(os::path_exists(&lib));
    assert!(is_rwx(&lib));
    let built_test = built_test_in_workspace(&temp_pkg_id, &workspace).expect(~"test_install_url");
    assert!(os::path_exists(&built_test));
    let built_bench = built_bench_in_workspace(&temp_pkg_id,
                                               &workspace).expect(~"test_install_url");
    assert!(os::path_exists(&built_bench));
    // And that the test and bench executables aren't installed
    let test = target_test_in_workspace(&temp_pkg_id, &workspace);
    assert!(!os::path_exists(&test));
    debug!("test = %s", test.to_str());
    let bench = target_bench_in_workspace(&temp_pkg_id, &workspace);
    debug!("bench = %s", bench.to_str());
    assert!(!os::path_exists(&bench));
}

#[test]
fn test_package_ids_must_be_relative_path_like() {
    use conditions::bad_pkg_id::cond;

    /*
    Okay:
    - One identifier, with no slashes
    - Several slash-delimited things, with no / at the root

    Not okay:
    - Empty string
    - Absolute path (as per os::is_absolute)

    */

    let default_version_str = "0.1";
    let addversion = |s| {
        fmt!("%s-%s", s, default_version_str)
    };

    let whatever = PkgId::new("foo");

    assert!(addversion("foo") == whatever.to_str());
    assert!(addversion("github.com/mozilla/rust") ==
            PkgId::new("github.com/mozilla/rust").to_str());

    do cond.trap(|(p, e)| {
        assert!("" == p.to_str());
        assert!("0-length pkgid" == e);
        copy whatever
    }).in {
        let x = PkgId::new("");
        assert!(addversion("foo") == x.to_str());
    }

    do cond.trap(|(p, e)| {
        assert!(p.to_str() == os::make_absolute(&Path("foo/bar/quux")).to_str());
        assert!("absolute pkgid" == e);
        copy whatever
    }).in {
        let z = PkgId::new(os::make_absolute(&Path("foo/bar/quux")).to_str());
        assert!(addversion("foo") == z.to_str());
    }

}
<commit_msg>rustpkg: ignore tests on i686, since they fail<commit_after>// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// rustpkg unit tests

use context::Ctx;
use core::hashmap::HashMap;
use core::path::Path;
use std::tempfile::mkdtemp;
use util::{PkgId, default_version};
use path_util::{target_executable_in_workspace, target_library_in_workspace,
               target_test_in_workspace, target_bench_in_workspace,
               make_dir_rwx, u_rwx, RemotePath, LocalPath, normalize,
               built_bench_in_workspace, built_test_in_workspace};

fn fake_ctxt(sysroot_opt: Option<@Path>) -> Ctx {
    Ctx {
        sysroot_opt: sysroot_opt,
        json: false,
        dep_cache: @mut HashMap::new()
    }
}

fn fake_pkg() -> PkgId {
    let sn = ~"bogus";
    let remote = RemotePath(Path(sn));
    PkgId {
        local_path: normalize(copy remote),
        remote_path: remote,
        short_name: sn,
        version: default_version()
    }
}

fn remote_pkg() -> PkgId {
    let remote = RemotePath(Path(~"github.com/catamorphism/test-pkg"));
    PkgId {
        local_path: normalize(copy remote),
        remote_path: remote,
        short_name: ~"test_pkg",
        version: default_version()
    }
}

fn writeFile(file_path: &Path, contents: ~str) {
    let out: @io::Writer =
        result::get(&io::file_writer(file_path,
                                     ~[io::Create, io::Truncate]));
    out.write_line(contents);
}

fn mk_temp_workspace(short_name: &LocalPath) -> Path {
    let workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
    // include version number in directory name
    let package_dir = workspace.push(~"src").push(fmt!("%s-0.1", short_name.to_str()));
    assert!(os::mkdir_recursive(&package_dir, u_rwx));
    // Create main, lib, test, and bench files
    writeFile(&package_dir.push(~"main.rs"),
              ~"fn main() { let _x = (); }");
    writeFile(&package_dir.push(~"lib.rs"),
              ~"pub fn f() { let _x = (); }");
    writeFile(&package_dir.push(~"test.rs"),
              ~"#[test] pub fn f() { (); }");
    writeFile(&package_dir.push(~"bench.rs"),
              ~"#[bench] pub fn f() { (); }");
    workspace
}

fn is_rwx(p: &Path) -> bool {
    use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR};

    match p.get_mode() {
        None => return false,
        Some(m) =>
            ((m & S_IRUSR as uint) == S_IRUSR as uint
            && (m & S_IWUSR as uint) == S_IWUSR as uint
            && (m & S_IXUSR as uint) == S_IXUSR as uint)
    }
}

fn test_sysroot() -> Path {
    // Totally gross hack but it's just for test cases.
    // Infer the sysroot from the exe name and pray that it's right.
    // (Did I mention it was a gross hack?)
    let self_path = os::self_exe_path().expect("Couldn't get self_exe path");
    self_path.pop()
}

// Ignored on i686 -- see #6517
#[test]
#[ignore(cfg(target_arch = "x86"))]
fn test_make_dir_rwx() {
    let temp = &os::tmpdir();
    let dir = temp.push(~"quux");
    assert!(!os::path_exists(&dir) ||
            os::remove_dir_recursive(&dir));
    debug!("Trying to make %s", dir.to_str());
    assert!(make_dir_rwx(&dir));
    assert!(os::path_is_dir(&dir));
    assert!(is_rwx(&dir));
    assert!(os::remove_dir_recursive(&dir));
}

#[test]
#[ignore(cfg(target_arch = "x86"))]
fn test_install_valid() {
    let sysroot = test_sysroot();
    debug!("sysroot = %s", sysroot.to_str());
    let ctxt = fake_ctxt(Some(@sysroot));
    let temp_pkg_id = fake_pkg();
    let temp_workspace = mk_temp_workspace(&temp_pkg_id.local_path);
    // should have test, bench, lib, and main
    ctxt.install(&temp_workspace, &temp_pkg_id);
    // Check that all files exist
    let exec = target_executable_in_workspace(&temp_pkg_id, &temp_workspace);
    debug!("exec = %s", exec.to_str());
    assert!(os::path_exists(&exec));
    assert!(is_rwx(&exec));
    let lib = target_library_in_workspace(&temp_pkg_id, &temp_workspace);
    debug!("lib = %s", lib.to_str());
    assert!(os::path_exists(&lib));
    assert!(is_rwx(&lib));
    // And that the test and bench executables aren't installed
    assert!(!os::path_exists(&target_test_in_workspace(&temp_pkg_id, &temp_workspace)));
    let bench = target_bench_in_workspace(&temp_pkg_id, &temp_workspace);
    debug!("bench = %s", bench.to_str());
    assert!(!os::path_exists(&bench));
}

#[test]
#[ignore(cfg(target_arch = "x86"))]
fn test_install_invalid() {
    use conditions::nonexistent_package::cond;
    use cond1 = conditions::missing_pkg_files::cond;

    let ctxt = fake_ctxt(None);
    let pkgid = fake_pkg();
    let temp_workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
    let mut error_occurred = false;
    let mut error1_occurred = false;
    do cond1.trap(|_| {
        error1_occurred = true;
    }).in {
        do cond.trap(|_| {
            error_occurred = true;
        }).in {
            ctxt.install(&temp_workspace, &pkgid);
        }
    }
    assert!(error_occurred && error1_occurred);
}

#[test]
#[ignore(cfg(target_arch = "x86"))]
fn test_install_url() {
    let workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir");
    let sysroot = test_sysroot();
    debug!("sysroot = %s", sysroot.to_str());
    let ctxt = fake_ctxt(Some(@sysroot));
    let temp_pkg_id = remote_pkg();
    // should have test, bench, lib, and main
    ctxt.install(&workspace, &temp_pkg_id);
    // Check that all files exist
    let exec = target_executable_in_workspace(&temp_pkg_id, &workspace);
    debug!("exec = %s", exec.to_str());
    assert!(os::path_exists(&exec));
    assert!(is_rwx(&exec));
    let lib = target_library_in_workspace(&temp_pkg_id, &workspace);
    debug!("lib = %s", lib.to_str());
    assert!(os::path_exists(&lib));
    assert!(is_rwx(&lib));
    let built_test = built_test_in_workspace(&temp_pkg_id, &workspace).expect(~"test_install_url");
    assert!(os::path_exists(&built_test));
    let built_bench = built_bench_in_workspace(&temp_pkg_id,
                                               &workspace).expect(~"test_install_url");
    assert!(os::path_exists(&built_bench));
    // And that the test and bench executables aren't installed
    let test = target_test_in_workspace(&temp_pkg_id, &workspace);
    assert!(!os::path_exists(&test));
    debug!("test = %s", test.to_str());
    let bench = target_bench_in_workspace(&temp_pkg_id, &workspace);
    debug!("bench = %s", bench.to_str());
    assert!(!os::path_exists(&bench));
}

#[test]
fn test_package_ids_must_be_relative_path_like() {
    use conditions::bad_pkg_id::cond;

    /*
    Okay:
    - One identifier, with no slashes
    - Several slash-delimited things, with no / at the root

    Not okay:
    - Empty string
    - Absolute path (as per os::is_absolute)

    */

    let default_version_str = "0.1";
    let addversion = |s| {
        fmt!("%s-%s", s, default_version_str)
    };

    let whatever = PkgId::new("foo");

    assert!(addversion("foo") == whatever.to_str());
    assert!(addversion("github.com/mozilla/rust") ==
            PkgId::new("github.com/mozilla/rust").to_str());

    do cond.trap(|(p, e)| {
        assert!("" == p.to_str());
        assert!("0-length pkgid" == e);
        copy whatever
    }).in {
        let x = PkgId::new("");
        assert!(addversion("foo") == x.to_str());
    }

    do cond.trap(|(p, e)| {
        assert!(p.to_str() == os::make_absolute(&Path("foo/bar/quux")).to_str());
        assert!("absolute pkgid" == e);
        copy whatever
    }).in {
        let z = PkgId::new(os::make_absolute(&Path("foo/bar/quux")).to_str());
        assert!(addversion("foo") == z.to_str());
    }

}
<|endoftext|>


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

#[allow(deprecated_mode)];

use json;
use sha1;
use serialize::{Encoder, Encodable, Decoder, Decodable};
use sort;

use core::cell::Cell;
use core::cmp;
use core::either::{Either, Left, Right};
use core::io;
use core::comm::{oneshot, PortOne, send_one};
use core::pipes::recv;
use core::prelude::*;
use core::result;
use core::run;
use core::hashmap::HashMap;
use core::task;
use core::to_bytes;

/**
*
* This is a loose clone of the fbuild build system, made a touch more
* generic (not wired to special cases on files) and much less metaprogram-y
* due to rust's comparative weakness there, relative to python.
*
* It's based around _imperative bulids_ that happen to have some function
* calls cached. That is, it's _just_ a mechanism for describing cached
* functions. This makes it much simpler and smaller than a "build system"
* that produces an IR and evaluates it. The evaluation order is normal
* function calls. Some of them just return really quickly.
*
* A cached function consumes and produces a set of _works_. A work has a
* name, a kind (that determines how the value is to be checked for
* freshness) and a value. Works must also be (de)serializable. Some
* examples of works:
*
*    kind   name    value
*   ------------------------
*    cfg    os      linux
*    file   foo.c   <sha1>
*    url    foo.com <etag>
*
* Works are conceptually single units, but we store them most of the time
* in maps of the form (type,name) => value. These are WorkMaps.
*
* A cached function divides the works it's interested up into inputs and
* outputs, and subdivides those into declared (input) works and
* discovered (input and output) works.
*
* A _declared_ input or is one that is given to the workcache before
* any work actually happens, in the "prep" phase. Even when a function's
* work-doing part (the "exec" phase) never gets called, it has declared
* inputs, which can be checked for freshness (and potentially
* used to determine that the function can be skipped).
*
* The workcache checks _all_ works for freshness, but uses the set of
* discovered outputs from the _previous_ exec (which it will re-discover
* and re-record each time the exec phase runs).
*
* Therefore the discovered works cached in the db might be a
* mis-approximation of the current discoverable works, but this is ok for
* the following reason: we assume that if an artifact A changed from
* depending on B,C,D to depending on B,C,D,E, then A itself changed (as
* part of the change-in-dependencies), so we will be ok.
*
* Each function has a single discriminated output work called its _result_.
* This is only different from other works in that it is returned, by value,
* from a call to the cacheable function; the other output works are used in
* passing to invalidate dependencies elsewhere in the cache, but do not
* otherwise escape from a function invocation. Most functions only have one
* output work anyways.
*
* A database (the central store of a workcache) stores a mappings:
*
* (fn_name,{declared_input}) => ({discovered_input},
*                                {discovered_output},result)
*
* (Note: fbuild, which workcache is based on, has the concept of a declared
* output as separate from a discovered output. This distinction exists only
* as an artifact of how fbuild works: via annotations on function types
* and metaprogramming, with explicit dependency declaration as a fallback.
* Workcache is more explicit about dependencies, and as such treats all
* outputs the same, as discovered-during-the-last-run.)
*
*/

#[deriving(Eq)]
#[auto_encode]
#[auto_decode]
struct WorkKey {
    kind: ~str,
    name: ~str
}

impl to_bytes::IterBytes for WorkKey {
    #[inline(always)]
    fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) {
        let mut flag = true;
        self.kind.iter_bytes(lsb0, |bytes| {flag = f(bytes); flag});
        if !flag { return; }
        self.name.iter_bytes(lsb0, f);
    }
}

impl cmp::Ord for WorkKey {
    fn lt(&self, other: &WorkKey) -> bool {
        self.kind < other.kind ||
            (self.kind == other.kind &&
             self.name < other.name)
    }
    fn le(&self, other: &WorkKey) -> bool {
        self.lt(other) || self.eq(other)
    }
    fn ge(&self, other: &WorkKey) -> bool {
        self.gt(other) || self.eq(other)
    }
    fn gt(&self, other: &WorkKey) -> bool {
        ! self.le(other)
    }
}

pub impl WorkKey {
    fn new(kind: &str, name: &str) -> WorkKey {
    WorkKey { kind: kind.to_owned(), name: name.to_owned() }
    }
}

struct WorkMap(HashMap<WorkKey, ~str>);

impl WorkMap {
    fn new() -> WorkMap { WorkMap(HashMap::new()) }
}

impl<S:Encoder> Encodable<S> for WorkMap {
    fn encode(&self, s: &S) {
        let mut d = ~[];
        for self.each |k, v| {
            d.push((copy *k, copy *v))
        }
        sort::tim_sort(d);
        d.encode(s)
    }
}

impl<D:Decoder> Decodable<D> for WorkMap {
    fn decode(d: &D) -> WorkMap {
        let v : ~[(WorkKey,~str)] = Decodable::decode(d);
        let mut w = WorkMap::new();
        for v.each |&(k, v)| {
            w.insert(copy k, copy v);
        }
        w
    }
}

struct Database {
    db_filename: Path,
    db_cache: HashMap<~str, ~str>,
    db_dirty: bool
}

pub impl Database {
    fn prepare(&mut self,
               fn_name: &str,
               declared_inputs: &WorkMap) -> Option<(WorkMap, WorkMap, ~str)>
    {
        let k = json_encode(&(fn_name, declared_inputs));
        match self.db_cache.find(&k) {
            None => None,
            Some(v) => Some(json_decode(*v))
        }
    }

    fn cache(&mut self,
             fn_name: &str,
             declared_inputs: &WorkMap,
             discovered_inputs: &WorkMap,
             discovered_outputs: &WorkMap,
             result: &str) {
        let k = json_encode(&(fn_name, declared_inputs));
        let v = json_encode(&(discovered_inputs,
                              discovered_outputs,
                              result));
        self.db_cache.insert(k,v);
        self.db_dirty = true
    }
}

struct Logger {
    // FIXME #4432: Fill in
    a: ()
}

pub impl Logger {
    fn info(&self, i: &str) {
        io::println(~"workcache: " + i.to_owned());
    }
}

struct Context {
    db: @mut Database,
    logger: @mut Logger,
    cfg: @json::Object,
    freshness: HashMap<~str,@fn(&str,&str)->bool>
}

struct Prep {
    ctxt: @Context,
    fn_name: ~str,
    declared_inputs: WorkMap,
}

struct Exec {
    discovered_inputs: WorkMap,
    discovered_outputs: WorkMap
}

struct Work<T> {
    prep: @mut Prep,
    res: Option<Either<T,PortOne<(Exec,T)>>>
}

fn json_encode<T:Encodable<json::Encoder>>(t: &T) -> ~str {
    do io::with_str_writer |wr| {
        t.encode(&json::Encoder(wr));
    }
}

// FIXME(#5121)
fn json_decode<T:Decodable<json::Decoder>>(s: &str) -> T {
    do io::with_str_reader(s) |rdr| {
        let j = result::unwrap(json::from_reader(rdr));
        Decodable::decode(&json::Decoder(j))
    }
}

fn digest<T:Encodable<json::Encoder>>(t: &T) -> ~str {
    let mut sha = sha1::sha1();
    sha.input_str(json_encode(t));
    sha.result_str()
}

fn digest_file(path: &Path) -> ~str {
    let mut sha = sha1::sha1();
    let s = io::read_whole_file_str(path);
    sha.input_str(*s.get_ref());
    sha.result_str()
}

pub impl Context {

    fn new(db: @mut Database,
                  lg: @mut Logger,
                  cfg: @json::Object) -> Context {
        Context {
            db: db,
            logger: lg,
            cfg: cfg,
            freshness: HashMap::new()
        }
    }

    fn prep<T:Owned +
              Encodable<json::Encoder> +
              Decodable<json::Decoder>>( // FIXME(#5121)
                  @self,
                  fn_name:&str,
                  blk: &fn(@mut Prep)->Work<T>) -> Work<T> {
        let p = @mut Prep {
            ctxt: self,
            fn_name: fn_name.to_owned(),
            declared_inputs: WorkMap::new()
        };
        blk(p)
    }
}

trait TPrep {
    fn declare_input(&mut self, kind:&str, name:&str, val:&str);
    fn is_fresh(&self, cat:&str, kind:&str, name:&str, val:&str) -> bool;
    fn all_fresh(&self, cat:&str, map:&WorkMap) -> bool;
    fn exec<T:Owned +
              Encodable<json::Encoder> +
              Decodable<json::Decoder>>( // FIXME(#5121)
        &self, blk: ~fn(&Exec) -> T) -> Work<T>;
}

impl TPrep for Prep {
    fn declare_input(&mut self, kind:&str, name:&str, val:&str) {
        self.declared_inputs.insert(WorkKey::new(kind, name),
                                 val.to_owned());
    }

    fn is_fresh(&self, cat: &str, kind: &str,
                name: &str, val: &str) -> bool {
        let k = kind.to_owned();
        let f = (*self.ctxt.freshness.get(&k))(name, val);
        let lg = self.ctxt.logger;
            if f {
                lg.info(fmt!("%s %s:%s is fresh",
                             cat, kind, name));
            } else {
                lg.info(fmt!("%s %s:%s is not fresh",
                             cat, kind, name))
            }
        f
    }

    fn all_fresh(&self, cat: &str, map: &WorkMap) -> bool {
        for map.each |k, v| {
            if ! self.is_fresh(cat, k.kind, k.name, *v) {
                return false;
            }
        }
        return true;
    }

    fn exec<T:Owned +
              Encodable<json::Encoder> +
              Decodable<json::Decoder>>( // FIXME(#5121)
            &self, blk: ~fn(&Exec) -> T) -> Work<T> {
        let mut bo = Some(blk);

        let cached = self.ctxt.db.prepare(self.fn_name, &self.declared_inputs);

        match cached {
            Some((ref disc_in, ref disc_out, ref res))
            if self.all_fresh("declared input",
                              &self.declared_inputs) &&
            self.all_fresh("discovered input", disc_in) &&
            self.all_fresh("discovered output", disc_out) => {
                Work::new(@mut *self, Left(json_decode(*res)))
            }

            _ => {
                let (chan, port) = oneshot::init();
                let mut blk = None;
                blk <-> bo;
                let blk = blk.unwrap();
                let chan = Cell(chan);

                do task::spawn || {
                    let exe = Exec {
                        discovered_inputs: WorkMap::new(),
                        discovered_outputs: WorkMap::new(),
                    };
                    let chan = chan.take();
                    let v = blk(&exe);
                    send_one(chan, (exe, v));
                }
                Work::new(@mut *self, Right(port))
            }
        }
    }
}

pub impl<T:Owned +
         Encodable<json::Encoder> +
         Decodable<json::Decoder>> Work<T> { // FIXME(#5121)
    fn new(p: @mut Prep, e: Either<T,PortOne<(Exec,T)>>) -> Work<T> {
        Work { prep: p, res: Some(e) }
    }
}

// FIXME (#3724): movable self. This should be in impl Work.
fn unwrap<T:Owned +
            Encodable<json::Encoder> +
            Decodable<json::Decoder>>( // FIXME(#5121)
        w: Work<T>) -> T {
    let mut ww = w;
    let mut s = None;

    ww.res <-> s;

    match s {
        None => fail!(),
        Some(Left(v)) => v,
        Some(Right(port)) => {
            let (exe, v) = match recv(port) {
                oneshot::send(data) => data
            };

            let s = json_encode(&v);

            let p = &*ww.prep;
            let db = p.ctxt.db;
            db.cache(p.fn_name,
                 &p.declared_inputs,
                 &exe.discovered_inputs,
                 &exe.discovered_outputs,
                 s);
            v
        }
    }
}

//#[test]
fn test() {
    use core::io::WriterUtil;

    let db = @mut Database { db_filename: Path("db.json"),
                             db_cache: HashMap::new(),
                             db_dirty: false };
    let lg = @mut Logger { a: () };
    let cfg = @HashMap::new();
    let cx = @Context::new(db, lg, cfg);
    let w:Work<~str> = do cx.prep("test1") |prep| {
        let pth = Path("foo.c");
        {
            let file = io::file_writer(&pth, [io::Create]).get();
            file.write_str("int main() { return 0; }");
        }

        prep.declare_input("file", pth.to_str(), digest_file(&pth));
        do prep.exec |_exe| {
            let out = Path("foo.o");
            run::run_program("gcc", [~"foo.c", ~"-o", out.to_str()]);
            out.to_str()
        }
    };
    let s = unwrap(w);
    io::println(s);
}
<commit_msg>auto merge of #5930 : osaut/rust/master, r=thestinger<commit_after>// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[allow(deprecated_mode)];

use json;
use sha1;
use serialize::{Encoder, Encodable, Decoder, Decodable};
use sort;

use core::cell::Cell;
use core::cmp;
use core::either::{Either, Left, Right};
use core::io;
use core::comm::{oneshot, PortOne, send_one};
use core::pipes::recv;
use core::prelude::*;
use core::result;
use core::run;
use core::hashmap::HashMap;
use core::task;
use core::to_bytes;

/**
*
* This is a loose clone of the [fbuild build system](https://github.com/felix-lang/fbuild),
* made a touch more generic (not wired to special cases on files) and much
* less metaprogram-y due to rust's comparative weakness there, relative to
* python.
*
* It's based around _imperative builds_ that happen to have some function
* calls cached. That is, it's _just_ a mechanism for describing cached
* functions. This makes it much simpler and smaller than a "build system"
* that produces an IR and evaluates it. The evaluation order is normal
* function calls. Some of them just return really quickly.
*
* A cached function consumes and produces a set of _works_. A work has a
* name, a kind (that determines how the value is to be checked for
* freshness) and a value. Works must also be (de)serializable. Some
* examples of works:
*
*    kind   name    value
*   ------------------------
*    cfg    os      linux
*    file   foo.c   <sha1>
*    url    foo.com <etag>
*
* Works are conceptually single units, but we store them most of the time
* in maps of the form (type,name) => value. These are WorkMaps.
*
* A cached function divides the works it's interested in into inputs and
* outputs, and subdivides those into declared (input) works and
* discovered (input and output) works.
*
* A _declared_ input or is one that is given to the workcache before
* any work actually happens, in the "prep" phase. Even when a function's
* work-doing part (the "exec" phase) never gets called, it has declared
* inputs, which can be checked for freshness (and potentially
* used to determine that the function can be skipped).
*
* The workcache checks _all_ works for freshness, but uses the set of
* discovered outputs from the _previous_ exec (which it will re-discover
* and re-record each time the exec phase runs).
*
* Therefore the discovered works cached in the db might be a
* mis-approximation of the current discoverable works, but this is ok for
* the following reason: we assume that if an artifact A changed from
* depending on B,C,D to depending on B,C,D,E, then A itself changed (as
* part of the change-in-dependencies), so we will be ok.
*
* Each function has a single discriminated output work called its _result_.
* This is only different from other works in that it is returned, by value,
* from a call to the cacheable function; the other output works are used in
* passing to invalidate dependencies elsewhere in the cache, but do not
* otherwise escape from a function invocation. Most functions only have one
* output work anyways.
*
* A database (the central store of a workcache) stores a mappings:
*
* (fn_name,{declared_input}) => ({discovered_input},
*                                {discovered_output},result)
*
* (Note: fbuild, which workcache is based on, has the concept of a declared
* output as separate from a discovered output. This distinction exists only
* as an artifact of how fbuild works: via annotations on function types
* and metaprogramming, with explicit dependency declaration as a fallback.
* Workcache is more explicit about dependencies, and as such treats all
* outputs the same, as discovered-during-the-last-run.)
*
*/

#[deriving(Eq)]
#[auto_encode]
#[auto_decode]
struct WorkKey {
    kind: ~str,
    name: ~str
}

impl to_bytes::IterBytes for WorkKey {
    #[inline(always)]
    fn iter_bytes(&self, lsb0: bool, f: to_bytes::Cb) {
        let mut flag = true;
        self.kind.iter_bytes(lsb0, |bytes| {flag = f(bytes); flag});
        if !flag { return; }
        self.name.iter_bytes(lsb0, f);
    }
}

impl cmp::Ord for WorkKey {
    fn lt(&self, other: &WorkKey) -> bool {
        self.kind < other.kind ||
            (self.kind == other.kind &&
             self.name < other.name)
    }
    fn le(&self, other: &WorkKey) -> bool {
        self.lt(other) || self.eq(other)
    }
    fn ge(&self, other: &WorkKey) -> bool {
        self.gt(other) || self.eq(other)
    }
    fn gt(&self, other: &WorkKey) -> bool {
        ! self.le(other)
    }
}

pub impl WorkKey {
    fn new(kind: &str, name: &str) -> WorkKey {
    WorkKey { kind: kind.to_owned(), name: name.to_owned() }
    }
}

struct WorkMap(HashMap<WorkKey, ~str>);

impl WorkMap {
    fn new() -> WorkMap { WorkMap(HashMap::new()) }
}

impl<S:Encoder> Encodable<S> for WorkMap {
    fn encode(&self, s: &S) {
        let mut d = ~[];
        for self.each |k, v| {
            d.push((copy *k, copy *v))
        }
        sort::tim_sort(d);
        d.encode(s)
    }
}

impl<D:Decoder> Decodable<D> for WorkMap {
    fn decode(d: &D) -> WorkMap {
        let v : ~[(WorkKey,~str)] = Decodable::decode(d);
        let mut w = WorkMap::new();
        for v.each |&(k, v)| {
            w.insert(copy k, copy v);
        }
        w
    }
}

struct Database {
    db_filename: Path,
    db_cache: HashMap<~str, ~str>,
    db_dirty: bool
}

pub impl Database {
    fn prepare(&mut self,
               fn_name: &str,
               declared_inputs: &WorkMap) -> Option<(WorkMap, WorkMap, ~str)>
    {
        let k = json_encode(&(fn_name, declared_inputs));
        match self.db_cache.find(&k) {
            None => None,
            Some(v) => Some(json_decode(*v))
        }
    }

    fn cache(&mut self,
             fn_name: &str,
             declared_inputs: &WorkMap,
             discovered_inputs: &WorkMap,
             discovered_outputs: &WorkMap,
             result: &str) {
        let k = json_encode(&(fn_name, declared_inputs));
        let v = json_encode(&(discovered_inputs,
                              discovered_outputs,
                              result));
        self.db_cache.insert(k,v);
        self.db_dirty = true
    }
}

struct Logger {
    // FIXME #4432: Fill in
    a: ()
}

pub impl Logger {
    fn info(&self, i: &str) {
        io::println(~"workcache: " + i.to_owned());
    }
}

struct Context {
    db: @mut Database,
    logger: @mut Logger,
    cfg: @json::Object,
    freshness: HashMap<~str,@fn(&str,&str)->bool>
}

struct Prep {
    ctxt: @Context,
    fn_name: ~str,
    declared_inputs: WorkMap,
}

struct Exec {
    discovered_inputs: WorkMap,
    discovered_outputs: WorkMap
}

struct Work<T> {
    prep: @mut Prep,
    res: Option<Either<T,PortOne<(Exec,T)>>>
}

fn json_encode<T:Encodable<json::Encoder>>(t: &T) -> ~str {
    do io::with_str_writer |wr| {
        t.encode(&json::Encoder(wr));
    }
}

// FIXME(#5121)
fn json_decode<T:Decodable<json::Decoder>>(s: &str) -> T {
    do io::with_str_reader(s) |rdr| {
        let j = result::unwrap(json::from_reader(rdr));
        Decodable::decode(&json::Decoder(j))
    }
}

fn digest<T:Encodable<json::Encoder>>(t: &T) -> ~str {
    let mut sha = sha1::sha1();
    sha.input_str(json_encode(t));
    sha.result_str()
}

fn digest_file(path: &Path) -> ~str {
    let mut sha = sha1::sha1();
    let s = io::read_whole_file_str(path);
    sha.input_str(*s.get_ref());
    sha.result_str()
}

pub impl Context {

    fn new(db: @mut Database,
                  lg: @mut Logger,
                  cfg: @json::Object) -> Context {
        Context {
            db: db,
            logger: lg,
            cfg: cfg,
            freshness: HashMap::new()
        }
    }

    fn prep<T:Owned +
              Encodable<json::Encoder> +
              Decodable<json::Decoder>>( // FIXME(#5121)
                  @self,
                  fn_name:&str,
                  blk: &fn(@mut Prep)->Work<T>) -> Work<T> {
        let p = @mut Prep {
            ctxt: self,
            fn_name: fn_name.to_owned(),
            declared_inputs: WorkMap::new()
        };
        blk(p)
    }
}

trait TPrep {
    fn declare_input(&mut self, kind:&str, name:&str, val:&str);
    fn is_fresh(&self, cat:&str, kind:&str, name:&str, val:&str) -> bool;
    fn all_fresh(&self, cat:&str, map:&WorkMap) -> bool;
    fn exec<T:Owned +
              Encodable<json::Encoder> +
              Decodable<json::Decoder>>( // FIXME(#5121)
        &self, blk: ~fn(&Exec) -> T) -> Work<T>;
}

impl TPrep for Prep {
    fn declare_input(&mut self, kind:&str, name:&str, val:&str) {
        self.declared_inputs.insert(WorkKey::new(kind, name),
                                 val.to_owned());
    }

    fn is_fresh(&self, cat: &str, kind: &str,
                name: &str, val: &str) -> bool {
        let k = kind.to_owned();
        let f = (*self.ctxt.freshness.get(&k))(name, val);
        let lg = self.ctxt.logger;
            if f {
                lg.info(fmt!("%s %s:%s is fresh",
                             cat, kind, name));
            } else {
                lg.info(fmt!("%s %s:%s is not fresh",
                             cat, kind, name))
            }
        f
    }

    fn all_fresh(&self, cat: &str, map: &WorkMap) -> bool {
        for map.each |k, v| {
            if ! self.is_fresh(cat, k.kind, k.name, *v) {
                return false;
            }
        }
        return true;
    }

    fn exec<T:Owned +
              Encodable<json::Encoder> +
              Decodable<json::Decoder>>( // FIXME(#5121)
            &self, blk: ~fn(&Exec) -> T) -> Work<T> {
        let mut bo = Some(blk);

        let cached = self.ctxt.db.prepare(self.fn_name, &self.declared_inputs);

        match cached {
            Some((ref disc_in, ref disc_out, ref res))
            if self.all_fresh("declared input",
                              &self.declared_inputs) &&
            self.all_fresh("discovered input", disc_in) &&
            self.all_fresh("discovered output", disc_out) => {
                Work::new(@mut *self, Left(json_decode(*res)))
            }

            _ => {
                let (chan, port) = oneshot::init();
                let mut blk = None;
                blk <-> bo;
                let blk = blk.unwrap();
                let chan = Cell(chan);

                do task::spawn || {
                    let exe = Exec {
                        discovered_inputs: WorkMap::new(),
                        discovered_outputs: WorkMap::new(),
                    };
                    let chan = chan.take();
                    let v = blk(&exe);
                    send_one(chan, (exe, v));
                }
                Work::new(@mut *self, Right(port))
            }
        }
    }
}

pub impl<T:Owned +
         Encodable<json::Encoder> +
         Decodable<json::Decoder>> Work<T> { // FIXME(#5121)
    fn new(p: @mut Prep, e: Either<T,PortOne<(Exec,T)>>) -> Work<T> {
        Work { prep: p, res: Some(e) }
    }
}

// FIXME (#3724): movable self. This should be in impl Work.
fn unwrap<T:Owned +
            Encodable<json::Encoder> +
            Decodable<json::Decoder>>( // FIXME(#5121)
        w: Work<T>) -> T {
    let mut ww = w;
    let mut s = None;

    ww.res <-> s;

    match s {
        None => fail!(),
        Some(Left(v)) => v,
        Some(Right(port)) => {
            let (exe, v) = match recv(port) {
                oneshot::send(data) => data
            };

            let s = json_encode(&v);

            let p = &*ww.prep;
            let db = p.ctxt.db;
            db.cache(p.fn_name,
                 &p.declared_inputs,
                 &exe.discovered_inputs,
                 &exe.discovered_outputs,
                 s);
            v
        }
    }
}

//#[test]
fn test() {
    use core::io::WriterUtil;

    let db = @mut Database { db_filename: Path("db.json"),
                             db_cache: HashMap::new(),
                             db_dirty: false };
    let lg = @mut Logger { a: () };
    let cfg = @HashMap::new();
    let cx = @Context::new(db, lg, cfg);
    let w:Work<~str> = do cx.prep("test1") |prep| {
        let pth = Path("foo.c");
        {
            let file = io::file_writer(&pth, [io::Create]).get();
            file.write_str("int main() { return 0; }");
        }

        prep.declare_input("file", pth.to_str(), digest_file(&pth));
        do prep.exec |_exe| {
            let out = Path("foo.o");
            run::run_program("gcc", [~"foo.c", ~"-o", out.to_str()]);
            out.to_str()
        }
    };
    let s = unwrap(w);
    io::println(s);
}
<|endoftext|>


<commit_before>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::env;
use std::path::PathBuf;

use habitat_win_users::account::Account;

extern "C" {
    pub fn GetUserTokenStatus() -> u32;
}

fn get_sid_by_name(name: &str) -> Option<String> {
    match Account::from_name(name) {
        Some(acct) => {
            match acct.sid.to_string() {
                Ok(username) => Some(username),
                Err(_) => None,
            }
        }
        None => None,
    }
}

pub fn get_uid_by_name(owner: &str) -> Option<String> {
    get_sid_by_name(owner)
}

pub fn get_gid_by_name(group: &str) -> Option<String> {
    get_sid_by_name(group)
}

pub fn get_current_username() -> Option<String> {
    match env::var("USERNAME").ok() {
        Some(username) => Some(username.to_lowercase()),
        None => None,
    }
}

// this is a no-op on windows
pub fn get_current_groupname() -> Option<String> {
    Some(String::new())
}

pub fn get_effective_uid() -> u32 {
    unsafe { GetUserTokenStatus() }
}

pub fn get_home_for_user(username: &str) -> Option<PathBuf> {
    unimplemented!();
}

pub fn get_primary_gid_for_user(username: &str) -> Option<u32> {
    None
}

pub fn root_level_account() -> String {
    env::var("COMPUTERNAME").unwrap().to_uppercase() + "$"
}

#[cfg(test)]
mod tests {
    use std::env;

    use super::*;

    #[test]
    fn downcase_current_username() {
        env::set_var("USERNAME", "uSer");
        assert_eq!(get_current_username().unwrap(), "user")
    }

    #[test]
    fn return_none_when_no_user() {
        env::remove_var("USERNAME");
        assert_eq!(get_current_username(), None)
    }
}
<commit_msg>patch up windows get_primary_gid_for_user<commit_after>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::env;
use std::path::PathBuf;

use habitat_win_users::account::Account;

extern "C" {
    pub fn GetUserTokenStatus() -> u32;
}

fn get_sid_by_name(name: &str) -> Option<String> {
    match Account::from_name(name) {
        Some(acct) => {
            match acct.sid.to_string() {
                Ok(username) => Some(username),
                Err(_) => None,
            }
        }
        None => None,
    }
}

pub fn get_uid_by_name(owner: &str) -> Option<String> {
    get_sid_by_name(owner)
}

pub fn get_gid_by_name(group: &str) -> Option<String> {
    get_sid_by_name(group)
}

pub fn get_current_username() -> Option<String> {
    match env::var("USERNAME").ok() {
        Some(username) => Some(username.to_lowercase()),
        None => None,
    }
}

// this is a no-op on windows
pub fn get_current_groupname() -> Option<String> {
    Some(String::new())
}

pub fn get_effective_uid() -> u32 {
    unsafe { GetUserTokenStatus() }
}

pub fn get_home_for_user(username: &str) -> Option<PathBuf> {
    unimplemented!();
}

pub fn get_primary_gid_for_user(username: &str) -> Option<String> {
    None
}

pub fn root_level_account() -> String {
    env::var("COMPUTERNAME").unwrap().to_uppercase() + "$"
}

#[cfg(test)]
mod tests {
    use std::env;

    use super::*;

    #[test]
    fn downcase_current_username() {
        env::set_var("USERNAME", "uSer");
        assert_eq!(get_current_username().unwrap(), "user")
    }

    #[test]
    fn return_none_when_no_user() {
        env::remove_var("USERNAME");
        assert_eq!(get_current_username(), None)
    }
}
<|endoftext|>


<commit_before>// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

//! Cerberus manifest containers.
//!
//! A "manifest container" is a frame around a Cerberus manifest that provides
//! certain security properties:
//! - Containers are signed, so that only the holder of a software signing key
//!   can create new manifests.
//! - Containers carry a monotinically increasing "version number", which the
//!   signing authority should take care to bump for each new manifest.
//!   Manticore components can refuse to load older manifest versions, as a
//!   form of rollback-attack hardening.
//!
//! # Wire Format
//!
//! ```text
//! struct Container {
//!     /// Total length, including the header.
//!     len: u16,
//!     /// The "magic number", which determines the manifest type.
//!     magic: u16,
//!     /// The monotonic id.
//!     id: u32,
//!     /// The length of the signature.
//!     sig_len: u16,
//!     /// Alignment padding.
//!     _: u16,
//!     /// The manifest-specific body.
//!     body: [u8; self.len - HEADER_LEN - self.sig_len],
//!     /// The cryptographic signature, an RSA signature in PKCS 1.5
//!     /// format.
//!     signature: [u8; self.sig_len],
//! }
//! ```
//!
//! This format is intended to be fully wire-compatible with Cerberus,
//! although the magic number and the manifest body may contain payloads that
//! are Manticore-specific.

use crate::crypto::rsa;
use crate::io::Read as _;
use crate::manifest::ManifestType;
use crate::manifest::Error;
use crate::protocol::wire::WireEnum;

/// Metadata for a [`Container`].
///
/// This struct describes metadata attached to every manifest, which makes up
/// part of the signed component.
///
/// [`Comtainer`]: struct.Container.html
#[derive(Debug)]
pub struct Metadata {
    /// The "version" or "manifest ID", a monotonically increasing integer that
    /// Manticore can use to protect against playback attacks, by refusing to
    /// load a manifest with a smaller version number.
    ///
    /// When minting a new manifest, a signing authority should make sure to
    /// bump this value.
    pub version_id: u32,
}

/// A parsed, verified, manifest container.
///
/// This type represents a generic, authenticated manifest. A value of this
/// type is a witness that authentication via signature was successful; it is
/// not possible to parse a `Container` without also verifying it.
///
/// See the [module-level documentation](index.html) for more information.
pub struct Container<'m> {
    manifest_type: ManifestType,
    metadata: Metadata,
    body: &'m [u8],
}

/// The length of the container header in bytes:
/// two halves, a word, another half, and two bytes of padding.
const HEADER_LEN: usize = 2 + 2 + 4 + 2 + 2;

impl<'m> Container<'m> {
    /// Parses and verifies a `Container` using the provided [`rsa::Engine`].
    ///
    /// This function first parses the `Container`'s header, which it uses for
    /// finding the signature at the end of the buffer.
    ///
    /// `buf` must be aligned to a four-byte boundary.
    ///
    /// [`rsa::Engine`]: ../../crypto/rsa/trait.Engine.html
    pub fn parse_and_verify<Rsa: rsa::Engine>(
        buf: &'m [u8],
        rsa: &mut Rsa,
    ) -> Result<Self, Error> {
        if buf.as_ptr().align_offset(4) != 0 {
            return Err(Error::Unaligned);
        }

        if HEADER_LEN > buf.len() {
            return Err(Error::OutOfRange);
        }

        let mut r = buf; // Use io::Read.
        let len = r.read_le::<u16>()? as usize;
        let magic = r.read_le::<u16>()?;
        let id = r.read_le::<u32>()?;
        let sig_len = r.read_le::<u16>()? as usize;

        // This length check, combined with the checked arithmetic below,
        // ensures that none of the slice index operations can panic.
        if len > buf.len() {
            return Err(Error::OutOfRange);
        }
        // Note that, because `HEADER_LEN` is a multiple of 4, the resulting
        // slice is 4-byte aligned (that is, the two bytes of padding get
        // sliced off in this operation).
        let rest = &buf[..len][HEADER_LEN..];

        let body_len = rest
            .len()
            .checked_sub(sig_len)
            .ok_or(Error::OutOfRange)?;
        let (body, sig) = rest.split_at(body_len);

        let signed_len =
            len.checked_sub(sig_len).ok_or(Error::OutOfRange)?;
        let signed = &buf[..signed_len];

        rsa.verify_signature(sig, signed)
            .map_err(|_| Error::SignatureFailure)?;

        Ok(Container {
            manifest_type: ManifestType::from_wire_value(magic)
                .ok_or(Error::OutOfRange)?,
            metadata: Metadata { version_id: id },
            body,
        })
    }

    /// Returns the [`ManifestType`] for this `Container`.
    ///
    /// [`ManifestType`]: ../enum.ManifestType.html
    pub fn manifest_type(&self) -> ManifestType {
        self.manifest_type
    }

    /// Checks whether this `Container` can replace `other`.
    ///
    /// In other words, `self` must:
    /// - Be of the same type as `other`.
    /// - Have a greater or equal `id` number than `other`.
    pub fn can_replace(&self, other: &Self) -> bool {
        self.manifest_type == other.manifest_type
            && self.metadata.version_id >= other.metadata.version_id
    }

    /// Returns this container's [`Metadata`] value.
    ///
    /// [`Metadata`]: struct.Metadata.html
    pub fn metadata(&self) -> &Metadata {
        &self.metadata
    }

    /// Returns the authenticated body of the `Container`.
    pub fn body(&self) -> &'m [u8] {
        self.body
    }
}

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

    use static_assertions::const_assert_eq;

    use crate::crypto::ring;
    use crate::crypto::rsa::Builder as _;
    use crate::crypto::rsa::Keypair as _;
    use crate::crypto::rsa::Signer as _;
    use crate::crypto::rsa::SignerBuilder as _;
    use crate::crypto::testdata;

    const MANIFEST_HEADER: &[u8] = &[
        0x1f, 0x01, // Total length. This is the header length (12) +
        //          // body length (19) + signature length (256).
        0x0e, 0xda, // FPM magic.
        0xaa, 0x55, 0x01, 0x00, // Container id (0x55aa).
        0x00, 0x01, // Signature length (0x100 = 256).
        0xff, 0xff, // Padding to 4 bytes.
    ];

    const MANIFEST_CONTENTS: &[u8] = b"Container contents!";
    const_assert_eq!(MANIFEST_CONTENTS.len(), 19);

    const MANIFEST_LEN: usize =
        MANIFEST_HEADER.len() + MANIFEST_CONTENTS.len() + 256;

    fn make_rsa_engine() -> (ring::rsa::Engine, ring::rsa::Signer) {
        let keypair =
            ring::rsa::Keypair::from_pkcs8(testdata::RSA_2048_PRIV_PKCS8)
                .unwrap();
        let pub_key = keypair.public();
        let rsa_builder = ring::rsa::Builder::new();
        let rsa = rsa_builder.new_engine(pub_key).unwrap();
        let signer = rsa_builder.new_signer(keypair).unwrap();
        (rsa, signer)
    }

    #[test]
    fn parse_manifest() {
        let (mut rsa, mut signer) = make_rsa_engine();

        let mut manifest = MANIFEST_HEADER.to_vec();
        manifest.extend_from_slice(MANIFEST_CONTENTS);

        let mut sig = vec![0; signer.pub_len().byte_len()];
        signer.sign(&manifest, &mut sig).unwrap();
        manifest.extend_from_slice(&sig);

        assert_eq!(manifest.len(), MANIFEST_LEN);

        let manifest =
            Container::parse_and_verify(&manifest, &mut rsa).unwrap();
        assert_eq!(manifest.manifest_type(), ManifestType::Fpm);
        assert_eq!(manifest.body(), MANIFEST_CONTENTS);
    }

    #[test]
    fn parse_manifest_too_short() {
        let (mut rsa, mut signer) = make_rsa_engine();

        let mut manifest = MANIFEST_HEADER.to_vec();
        manifest.extend_from_slice(&MANIFEST_CONTENTS[1..]);

        let mut sig = vec![0; signer.pub_len().byte_len()];
        signer.sign(&manifest, &mut sig).unwrap();
        manifest.extend_from_slice(&sig);

        assert_eq!(manifest.len(), MANIFEST_LEN - 1);

        assert!(Container::parse_and_verify(&manifest, &mut rsa).is_err());
    }

    #[test]
    fn parse_manifest_bad_sig() {
        let (mut rsa, mut signer) = make_rsa_engine();

        let mut manifest = MANIFEST_HEADER.to_vec();
        manifest.extend_from_slice(MANIFEST_CONTENTS);

        let mut sig = vec![0; signer.pub_len().byte_len()];
        signer.sign(&manifest, &mut sig).unwrap();
        // Flip a bit in the signature.
        sig[0] ^= 1;
        manifest.extend_from_slice(&sig);

        assert_eq!(manifest.len(), MANIFEST_LEN);

        assert!(matches!(
            Container::parse_and_verify(&manifest, &mut rsa),
            Err(Error::SignatureFailure)
        ));
    }
}
<commit_msg>[manifest] Add Containerizer, a manifest builder<commit_after>// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

//! Cerberus manifest containers.
//!
//! A "manifest container" is a frame around a Cerberus manifest that provides
//! certain security properties:
//! - Containers are signed, so that only the holder of a software signing key
//!   can create new manifests.
//! - Containers carry a monotinically increasing "version number", which the
//!   signing authority should take care to bump for each new manifest.
//!   Manticore components can refuse to load older manifest versions, as a
//!   form of rollback-attack hardening.
//!
//! # Wire Format
//!
//! ```text
//! struct Container {
//!     /// Total length, including the header.
//!     len: u16,
//!     /// The "magic number", which determines the manifest type.
//!     magic: u16,
//!     /// The monotonic id.
//!     id: u32,
//!     /// The length of the signature.
//!     sig_len: u16,
//!     /// Alignment padding.
//!     _: u16,
//!     /// The manifest-specific body.
//!     body: [u8; self.len - HEADER_LEN - self.sig_len],
//!     /// The cryptographic signature, an RSA signature in PKCS 1.5
//!     /// format.
//!     signature: [u8; self.sig_len],
//! }
//! ```
//!
//! This format is intended to be fully wire-compatible with Cerberus,
//! although the magic number and the manifest body may contain payloads that
//! are Manticore-specific.

use crate::crypto::rsa;
use crate::io;
use crate::io::cursor::SeekPos;
use crate::io::Cursor;
use crate::io::Read as _;
use crate::io::Write;
use crate::manifest::Error;
use crate::manifest::ManifestType;
use crate::protocol::wire::WireEnum;

/// Metadata for a [`Container`].
///
/// This struct describes metadata attached to every manifest, which makes up
/// part of the signed component.
///
/// [`Comtainer`]: struct.Container.html
#[derive(Debug)]
pub struct Metadata {
    /// The "version" or "manifest ID", a monotonically increasing integer that
    /// Manticore can use to protect against playback attacks, by refusing to
    /// load a manifest with a smaller version number.
    ///
    /// When minting a new manifest, a signing authority should make sure to
    /// bump this value.
    pub version_id: u32,
}

/// A parsed, verified, manifest container.
///
/// This type represents a generic, authenticated manifest. A value of this
/// type is a witness that authentication via signature was successful; it is
/// not possible to parse a `Container` without also verifying it.
///
/// See the [module-level documentation](index.html) for more information.
pub struct Container<'m> {
    manifest_type: ManifestType,
    metadata: Metadata,
    body: &'m [u8],
}

/// Offsets for fields within the container header.
const LEN_OFFSET: usize = 0;
const TYPE_OFFSET: usize = 2;
const ID_OFFSET: usize = 4;
const SIG_LEN_OFFSET: usize = 8;

/// The length of the container header in bytes:
/// two halves, a word, another half, and two bytes of padding.
const HEADER_LEN: usize = 2 + 2 + 4 + 2 + 2;

impl<'m> Container<'m> {
    /// Parses and verifies a `Container` using the provided [`rsa::Engine`].
    ///
    /// This function first parses the `Container`'s header, which it uses for
    /// finding the signature at the end of the buffer.
    ///
    /// `buf` must be aligned to a four-byte boundary.
    ///
    /// [`rsa::Engine`]: ../../crypto/rsa/trait.Engine.html
    pub fn parse_and_verify<Rsa: rsa::Engine>(
        buf: &'m [u8],
        rsa: &mut Rsa,
    ) -> Result<Self, Error> {
        if buf.as_ptr().align_offset(4) != 0 {
            return Err(Error::Unaligned);
        }

        if HEADER_LEN > buf.len() {
            return Err(Error::OutOfRange);
        }

        let mut r = buf; // Use io::Read.
        let len = r.read_le::<u16>()? as usize;
        let magic = r.read_le::<u16>()?;
        let id = r.read_le::<u32>()?;
        let sig_len = r.read_le::<u16>()? as usize;

        // This length check, combined with the checked arithmetic below,
        // ensures that none of the slice index operations can panic.
        if len > buf.len() {
            return Err(Error::OutOfRange);
        }
        // Note that, because `HEADER_LEN` is a multiple of 4, the resulting
        // slice is 4-byte aligned (that is, the two bytes of padding get
        // sliced off in this operation).
        let rest = &buf[..len][HEADER_LEN..];

        let body_len =
            rest.len().checked_sub(sig_len).ok_or(Error::OutOfRange)?;
        let (body, sig) = rest.split_at(body_len);

        let signed_len = len.checked_sub(sig_len).ok_or(Error::OutOfRange)?;
        let signed = &buf[..signed_len];

        rsa.verify_signature(sig, signed)
            .map_err(|_| Error::SignatureFailure)?;

        Ok(Container {
            manifest_type: ManifestType::from_wire_value(magic)
                .ok_or(Error::OutOfRange)?,
            metadata: Metadata { version_id: id },
            body,
        })
    }

    /// Returns the [`ManifestType`] for this `Container`.
    ///
    /// [`ManifestType`]: ../enum.ManifestType.html
    pub fn manifest_type(&self) -> ManifestType {
        self.manifest_type
    }

    /// Checks whether this `Container` can replace `other`.
    ///
    /// In other words, `self` must:
    /// - Be of the same type as `other`.
    /// - Have a greater or equal `id` number than `other`.
    pub fn can_replace(&self, other: &Self) -> bool {
        self.manifest_type == other.manifest_type
            && self.metadata.version_id >= other.metadata.version_id
    }

    /// Returns this container's [`Metadata`] value.
    ///
    /// [`Metadata`]: struct.Metadata.html
    pub fn metadata(&self) -> &Metadata {
        &self.metadata
    }

    /// Returns the authenticated body of the `Container`.
    pub fn body(&self) -> &'m [u8] {
        self.body
    }

    /// Re-serializes this `Container` into its binary format.
    ///
    /// Re-serialization will not be exact; in particular, having the same
    /// signature will depend on having the original signing keys.
    ///
    /// For more fine-grained control of serialiation, see [`Containerizer`].
    ///
    /// [`Containerizer`]: struct.Containerizer.html
    pub fn containerize<'buf>(
        &self,
        signer: &mut impl rsa::Signer,
        buf: &'buf mut [u8],
    ) -> Result<&'buf mut [u8], Error> {
        let mut builder = Containerizer::new(buf)?
            .with_type(self.manifest_type())?
            .with_metadata(self.metadata())?;
        builder.write_bytes(self.body())?;
        builder.sign(signer)
    }
}

/// A [`Write`] implementation for writing new manifest containers.
///
/// Once all the parts of the container have been initialized, `sign()` can
/// be used to produce an authentic, encoded manifest.
///
/// [`Write`]: ../../io/trait.Write.html
pub struct Containerizer<'m> {
    // Invariant: at all times, the pointer within the cursor should point
    // beyond the "header" portion; `seek()` is used to move the cursor back
    // when modifying the header.
    cursor: Cursor<'m>,

    has_type: bool,
    has_metadata: bool,
}

impl<'m> Containerizer<'m> {
    /// Creates a new, empty containerizer.
    ///
    /// This function does not perform I/O, but it will verify that `out`
    /// is at least large enough to hold a [`Container`] header.
    ///
    /// Typically, this function will followed up by calls to other
    /// `Containerizer` functions before it is used as a [`Write`].
    ///
    /// # Example
    /// ```
    /// # use manticore::manifest::{*, container::*};
    /// # use manticore::io::Write as _;
    /// # let mut buf = [0; 64];
    /// let mut builder = Containerizer::new(&mut buf)?
    ///     .with_type(ManifestType::Fpm)?
    ///     .with_metadata(&Metadata { version_id: 42 })?;
    /// builder.write_bytes(b"manifest contents stuff")?;
    /// # Ok::<(), Error>(())
    /// ```
    ///
    /// [`Container`]: struct.Container.html
    /// [`Write`]: ../../io/trait.Write.html
    pub fn new(out: &'m mut [u8]) -> Result<Self, Error> {
        let mut cursor = Cursor::new(out);
        cursor.seek(SeekPos::Abs(HEADER_LEN))?;

        Ok(Self {
            cursor,
            has_type: false,
            has_metadata: false,
        })
    }

    /// Writes the given [`ManifestType`] into this `Containerizer`.
    ///
    /// [`ManifestType`]: ../enum.ManifestType.html
    #[inline]
    pub fn with_type(
        mut self,
        manifest_type: ManifestType,
    ) -> Result<Self, Error> {
        let mark = self.cursor.consumed_len();
        self.cursor.seek(SeekPos::Abs(TYPE_OFFSET))?;
        self.cursor.write_le(manifest_type.to_wire_value())?;
        self.cursor.seek(SeekPos::Abs(mark))?;

        self.has_type = true;
        Ok(self)
    }

    /// Writes the given [`Metadata`] into this `Containerizer`.
    ///
    /// [`Metadata`]: struct.Metadata.html
    #[inline]
    pub fn with_metadata(mut self, metadata: &Metadata) -> Result<Self, Error> {
        let mark = self.cursor.consumed_len();
        self.cursor.seek(SeekPos::Abs(ID_OFFSET))?;
        self.cursor.write_le(metadata.version_id)?;
        self.cursor.seek(SeekPos::Abs(mark))?;

        self.has_metadata = true;
        Ok(self)
    }

    /// Completes the containerization process by signing all of the contents
    /// and producing an encoded [`Container`].
    ///
    /// `with_type()` and `with_metadata()` must have been called before this
    /// function is called; otherwise, an error will be returned.
    ///
    /// [`Container`]: struct.Container.html
    pub fn sign(
        mut self,
        signer: &mut impl rsa::Signer,
    ) -> Result<&'m mut [u8], Error> {
        if !self.has_type || !self.has_metadata {
            return Err(Error::OutOfRange);
        }

        let sig_len = signer.pub_len().byte_len();
        let total_len = self
            .cursor
            .consumed_len()
            .checked_add(sig_len)
            .ok_or(io::Error::BufferExhausted)?;

        let u16_max = u16::MAX as usize;
        if total_len > u16_max || sig_len > u16_max {
            return Err(Error::OutOfRange);
        }

        let mark = self.cursor.consumed_len();
        self.cursor.seek(SeekPos::Abs(LEN_OFFSET))?;
        self.cursor.write_le(total_len as u16)?;
        self.cursor.seek(SeekPos::Abs(SIG_LEN_OFFSET))?;
        self.cursor.write_le(sig_len as u16)?;
        // Always set the "padding" bytes to 0xff.
        self.cursor.write_le(0xffffu16)?;
        self.cursor.seek(SeekPos::Abs(mark))?;

        let (message, sig) = self.cursor.consume_with_prior(sig_len)?;
        signer
            .sign(message, sig)
            .map_err(|_| Error::SignatureFailure)?;
        Ok(self.cursor.take_consumed_bytes())
    }
}

impl Write for Containerizer<'_> {
    fn write_bytes(&mut self, buf: &[u8]) -> Result<(), io::Error> {
        self.cursor.write_bytes(buf)
    }
}

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

    use static_assertions::const_assert_eq;

    use crate::crypto::ring;
    use crate::crypto::rsa::Builder as _;
    use crate::crypto::rsa::Keypair as _;
    use crate::crypto::rsa::Signer as _;
    use crate::crypto::rsa::SignerBuilder as _;
    use crate::crypto::testdata;

    const MANIFEST_HEADER: &[u8] = &[
        0x1f, 0x01, // Total length. This is the header length (12) +
        //          // body length (19) + signature length (256).
        0x0e, 0xda, // FPM magic.
        0xaa, 0x55, 0x01, 0x00, // Container id (0x155aa).
        0x00, 0x01, // Signature length (0x100 = 256).
        0xff, 0xff, // Padding to 4 bytes.
    ];

    const MANIFEST_CONTENTS: &[u8] = b"Container contents!";
    const_assert_eq!(MANIFEST_CONTENTS.len(), 19);

    const MANIFEST_LEN: usize =
        MANIFEST_HEADER.len() + MANIFEST_CONTENTS.len() + 256;

    fn make_rsa_engine() -> (ring::rsa::Engine, ring::rsa::Signer) {
        let keypair =
            ring::rsa::Keypair::from_pkcs8(testdata::RSA_2048_PRIV_PKCS8)
                .unwrap();
        let pub_key = keypair.public();
        let rsa_builder = ring::rsa::Builder::new();
        let rsa = rsa_builder.new_engine(pub_key).unwrap();
        let signer = rsa_builder.new_signer(keypair).unwrap();
        (rsa, signer)
    }

    #[test]
    fn parse_manifest() {
        let (mut rsa, mut signer) = make_rsa_engine();

        let mut manifest = MANIFEST_HEADER.to_vec();
        manifest.extend_from_slice(MANIFEST_CONTENTS);

        let mut sig = vec![0; signer.pub_len().byte_len()];
        signer.sign(&manifest, &mut sig).unwrap();
        manifest.extend_from_slice(&sig);

        assert_eq!(manifest.len(), MANIFEST_LEN);

        let manifest =
            Container::parse_and_verify(&manifest, &mut rsa).unwrap();
        assert_eq!(manifest.manifest_type(), ManifestType::Fpm);
        assert_eq!(manifest.body(), MANIFEST_CONTENTS);
    }

    #[test]
    fn parse_manifest_too_short() {
        let (mut rsa, mut signer) = make_rsa_engine();

        let mut manifest = MANIFEST_HEADER.to_vec();
        manifest.extend_from_slice(&MANIFEST_CONTENTS[1..]);

        let mut sig = vec![0; signer.pub_len().byte_len()];
        signer.sign(&manifest, &mut sig).unwrap();
        manifest.extend_from_slice(&sig);

        assert_eq!(manifest.len(), MANIFEST_LEN - 1);

        assert!(Container::parse_and_verify(&manifest, &mut rsa).is_err());
    }

    #[test]
    fn parse_manifest_bad_sig() {
        let (mut rsa, mut signer) = make_rsa_engine();

        let mut manifest = MANIFEST_HEADER.to_vec();
        manifest.extend_from_slice(MANIFEST_CONTENTS);

        let mut sig = vec![0; signer.pub_len().byte_len()];
        signer.sign(&manifest, &mut sig).unwrap();
        // Flip a bit in the signature.
        sig[0] ^= 1;
        manifest.extend_from_slice(&sig);

        assert_eq!(manifest.len(), MANIFEST_LEN);

        assert!(matches!(
            Container::parse_and_verify(&manifest, &mut rsa),
            Err(Error::SignatureFailure)
        ));
    }

    #[test]
    fn build_manifest() {
        let keypair =
            ring::rsa::Keypair::from_pkcs8(testdata::RSA_2048_PRIV_PKCS8)
                .unwrap();
        let pub_key = keypair.public();
        let rsa_builder = ring::rsa::Builder::new();
        let mut signer = rsa_builder.new_signer(keypair).unwrap();
        let mut rsa = rsa_builder.new_engine(pub_key).unwrap();

        let mut buf = vec![0; 1024];
        let mut builder = Containerizer::new(&mut buf)
            .unwrap()
            .with_type(ManifestType::Fpm)
            .unwrap()
            .with_metadata(&Metadata {
                version_id: 0x155aa,
            })
            .unwrap();
        builder.write_bytes(MANIFEST_CONTENTS).unwrap();
        let manifest_bytes = builder.sign(&mut signer).unwrap();
        assert_eq!(
            &manifest_bytes[..HEADER_LEN],
            &MANIFEST_HEADER[..HEADER_LEN]
        );

        let manifest =
            Container::parse_and_verify(manifest_bytes, &mut rsa).unwrap();
        assert_eq!(manifest.manifest_type(), ManifestType::Fpm);
        assert_eq!(manifest.metadata().version_id, 0x155aa);
        assert_eq!(manifest.body(), MANIFEST_CONTENTS);
    }

    #[test]
    fn roumd_trip() {
        let keypair =
            ring::rsa::Keypair::from_pkcs8(testdata::RSA_2048_PRIV_PKCS8)
                .unwrap();
        let pub_key = keypair.public();
        let rsa_builder = ring::rsa::Builder::new();
        let mut signer = rsa_builder.new_signer(keypair).unwrap();
        let mut rsa = rsa_builder.new_engine(pub_key).unwrap();

        let mut manifest = MANIFEST_HEADER.to_vec();
        manifest.extend_from_slice(MANIFEST_CONTENTS);

        let mut sig = vec![0; signer.pub_len().byte_len()];
        signer.sign(&manifest, &mut sig).unwrap();

        manifest.extend_from_slice(&sig);
        assert_eq!(manifest.len(), MANIFEST_LEN);

        let parsed_manifest =
            Container::parse_and_verify(&manifest, &mut rsa).unwrap();

        let mut buf = vec![0; 512];
        let new_manifest_bytes =
            parsed_manifest.containerize(&mut signer, &mut buf).unwrap();
        // Note that this assumes that the padding bytes are always 0xffff.
        assert_eq!(&manifest[..], new_manifest_bytes);
    }
}
<|endoftext|>