<commit_before>//! Crate to `#include` C++ headers in your Rust code, and generate
//! idiomatic bindings using `cxx`. See [include_cpp] for details.

// Copyright 2020 Google LLC
//
// 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
//
//    https://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.

#[cfg(test)]
mod reduce_test;

use std::{
    fs::File,
    io::Write,
    os::unix::prelude::PermissionsExt,
    path::{Path, PathBuf},
    process::Command,
};

use autocxx_engine::{get_clang_path, make_clang_args, preprocess};
use clap::{crate_authors, crate_version, App, Arg, ArgMatches};
use indoc::indoc;
use itertools::Itertools;
use tempfile::TempDir;

static LONG_HELP: &str = indoc! {"
Command line utility to minimize autocxx bug cases.

This is a wrapper for creduce.

Example command-line:
autocxx-reduce -I my-inc-dir -h my-header -d 'generate!(\"MyClass\")' -k -- --n 64
"};

fn main() {
    let matches = App::new("autocxx-reduce")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Reduce a C++ test case")
        .long_about(LONG_HELP)
        .arg(
            Arg::with_name("inc")
                .short("I")
                .long("inc")
                .multiple(true)
                .number_of_values(1)
                .value_name("INCLUDE DIRS")
                .help("include path")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("define")
                .short("D")
                .long("define")
                .multiple(true)
                .number_of_values(1)
                .value_name("DEFINE")
                .help("macro definition")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("header")
                .short("h")
                .long("header")
                .multiple(true)
                .number_of_values(1)
                .required(true)
                .value_name("HEADER")
                .help("header file name")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("directive")
                .short("d")
                .long("directive")
                .multiple(true)
                .number_of_values(1)
                .value_name("DIRECTIVE")
                .help("directives to put within include_cpp!")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("problem")
                .short("p")
                .long("problem")
                .required(true)
                .value_name("PROBLEM")
                .help("problem string we're looking for... may be in logs, or in generated C++, or generated .rs")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("creduce")
                .long("creduce")
                .required(true)
                .value_name("PATH")
                .help("creduce binary location")
                .default_value("creduce")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("output")
                .short("o")
                .long("output")
                .value_name("OUTPUT")
                .help("where to write minimized output")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("gen-cmd")
                .short("g")
                .long("gen-cmd")
                .value_name("GEN-CMD")
                .help("where to find autocxx-gen")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("keep")
                .short("k")
                .long("keep-dir")
                .help("keep the temporary directory for debugging purposes"),
        )
        .arg(
            Arg::with_name("clang-args")
                .short("c")
                .long("clang-arg")
                .multiple(true)
                .value_name("CLANG_ARG")
                .help("Extra arguments to pass to Clang"),
        )
        .arg(
            Arg::with_name("no-precompile")
                .long("no-precompile")
                .help("Do not precompile the C++ header before passing to autocxxgen"),
        )
        .arg(
            Arg::with_name("no-postcompile")
                .long("no-postcompile")
                .help("Do not post-compile the C++ generated by autocxxgen"),
        )
        .arg(Arg::with_name("creduce-args").last(true).multiple(true))
        .get_matches();
    run(matches).unwrap();
}

fn run(matches: ArgMatches) -> Result<(), std::io::Error> {
    let keep_tmp = matches.is_present("keep");
    let tmp_dir = TempDir::new()?;
    let r = do_run(matches, &tmp_dir);
    if keep_tmp {
        println!(
            "Keeping temp dir created at: {}",
            tmp_dir.into_path().to_str().unwrap()
        );
    }
    r
}

fn do_run(matches: ArgMatches, tmp_dir: &TempDir) -> Result<(), std::io::Error> {
    let incs: Vec<_> = matches
        .values_of("inc")
        .unwrap_or_default()
        .map(PathBuf::from)
        .collect();
    let defs: Vec<_> = matches.values_of("define").unwrap_or_default().collect();
    let headers: Vec<_> = matches.values_of("header").unwrap_or_default().collect();
    let listing_path = tmp_dir.path().join("listing.h");
    create_concatenated_header(&headers, &listing_path)?;
    let concat_path = tmp_dir.path().join("concat.h");
    announce_progress(&format!(
        "Preprocessing {:?} to {:?}",
        listing_path, concat_path
    ));
    preprocess(&listing_path, &concat_path, &incs, &defs)?;
    let rs_path = tmp_dir.path().join("input.rs");
    let directives: Vec<_> = std::iter::once("#include \"concat.h\"\n".to_string())
        .chain(
            matches
                .values_of("directive")
                .unwrap_or_default()
                .map(|s| format!("{}\n", s)),
        )
        .collect();
    create_rs_file(&rs_path, &directives)?;
    let extra_clang_args: Vec<_> = matches
        .values_of("clang-args")
        .unwrap_or_default()
        .collect();
    let default_gen_cmd = std::env::current_exe()?
        .parent()
        .unwrap()
        .join("autocxx-gen")
        .to_str()
        .unwrap()
        .to_string();
    let gen_cmd = matches.value_of("gen-cmd").unwrap_or(&default_gen_cmd);
    run_sample_gen_cmd(gen_cmd, &rs_path, &tmp_dir.path(), &extra_clang_args)?;
    let interestingness_test = tmp_dir.path().join("test.sh");
    create_interestingness_test(
        gen_cmd,
        &interestingness_test,
        matches.value_of("problem").unwrap(),
        &rs_path,
        &extra_clang_args,
        matches.value_of("no-precompile").is_none(),
        matches.value_of("no-postcompile").is_none(),
    )?;
    run_creduce(
        matches.value_of("creduce").unwrap(),
        &interestingness_test,
        &concat_path,
        matches.values_of("creduce-args").unwrap_or_default(),
    );
    announce_progress("creduce completed");
    let output_path = matches.value_of("output");
    match output_path {
        None => print_minimized_case(&concat_path)?,
        Some(output_path) => {
            std::fs::copy(&concat_path, &PathBuf::from(output_path))?;
        }
    };
    Ok(())
}

fn announce_progress(msg: &str) {
    println!("=== {} ===", msg);
}

fn print_minimized_case(concat_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Completed. Minimized test case:");
    let contents = std::fs::read_to_string(concat_path)?;
    println!("{}", contents);
    Ok(())
}

/// Arguments we pass to creduce if supported. This pass always seems to cause a crash
/// as far as I can tell, so always exclude it. It may be environment-dependent,
/// of course, but as I'm the primary user of this tool I am ruthlessly removing it.
const REMOVE_PASS_LINE_MARKERS: &[&str] = &["--remove-pass", "pass_line_markers", "*"];
const SKIP_INITIAL_PASSES: &[&str] = &["--skip-initial-passes"];

fn creduce_supports_remove_pass(creduce_cmd: &str) -> bool {
    let msg = Command::new(creduce_cmd)
        .arg("--help")
        .output()
        .unwrap()
        .stdout;
    let msg = std::str::from_utf8(&msg).unwrap();
    msg.contains("--remove-pass")
}

fn run_creduce<'a>(
    creduce_cmd: &str,
    interestingness_test: &'a Path,
    concat_path: &'a Path,
    creduce_args: impl Iterator<Item = &'a str>,
) {
    announce_progress("creduce");
    let args = std::iter::once(interestingness_test.to_str().unwrap())
        .chain(std::iter::once(concat_path.to_str().unwrap()))
        .chain(creduce_args)
        .chain(
            if creduce_supports_remove_pass(creduce_cmd) {
                REMOVE_PASS_LINE_MARKERS
            } else {
                SKIP_INITIAL_PASSES
            }
            .iter()
            .copied(),
        )
        .collect::<Vec<_>>();
    println!("Command: {} {}", creduce_cmd, args.join(" "));
    Command::new(creduce_cmd)
        .args(args)
        .status()
        .expect("failed to creduce");
}

fn run_sample_gen_cmd(
    gen_cmd: &str,
    rs_file: &Path,
    tmp_dir: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    let args = format_gen_cmd(rs_file, tmp_dir.to_str().unwrap(), extra_clang_args);
    let args = args.collect::<Vec<_>>();
    let args_str = args.join(" ");
    announce_progress(&format!("Running sample gen cmd: {} {}", gen_cmd, args_str));
    Command::new(gen_cmd).args(args).status()?;
    Ok(())
}

fn format_gen_cmd<'a>(
    rs_file: &Path,
    dir: &str,
    extra_clang_args: &'a [&str],
) -> impl Iterator<Item = String> + 'a {
    let args = [
        "-o".to_string(),
        dir.to_string(),
        "-I".to_string(),
        dir.to_string(),
        rs_file.to_str().unwrap().to_string(),
        "--gen-rs-complete".to_string(),
        "--gen-cpp".to_string(),
        "--".to_string(),
    ]
    .to_vec();
    args.into_iter()
        .chain(extra_clang_args.iter().map(|s| s.to_string()))
}

fn create_interestingness_test(
    gen_cmd: &str,
    test_path: &Path,
    problem: &str,
    rs_file: &Path,
    extra_clang_args: &[&str],
    precompile: bool,
    postcompile: bool,
) -> Result<(), std::io::Error> {
    announce_progress("Creating interestingness test");
    // Ensure we refer to the input header by relative path
    // because creduce will invoke us in some other directory with
    // a copy thereof.
    let mut args = format_gen_cmd(rs_file, "$(pwd)", extra_clang_args);
    let args = args.join(" ");
    let precompile_step = make_compile_step(precompile, "concat.h", extra_clang_args);
    // For the compile afterwards, we have to avoid including any system headers.
    // We rely on equivalent content being hermetically inside concat.h.
    let postcompile_step = format!(
        "; mv gen0.cc gen0-orig.cc && sed -e 's/#include <.*>//g' < gen0-orig.cc > gen0.cc && mv autocxxgen.h autocxxgen-orig.h && sed -e 's/#include <.*>//g' < autocxxgen-orig.h > autocxxgen.h && {} 2>&1",
        make_compile_step(postcompile, "gen0.cc", extra_clang_args)
    );
    let content = format!(
        indoc! {"
        #!/bin/sh
        set -e
        echo Precompile
        {}
        echo Move
        mv concat.h concat-body.h
        echo Codegen
        (echo \"#ifndef __CONCAT_H__\"; echo \"#define __CONCAT_H__\"; echo '#include \"concat-body.h\"'; echo \"#endif\") > concat.h
        ({} {} 2>&1 && cat gen.complete.rs && cat autocxxgen*.h {}) | grep \"{}\"  >/dev/null 2>&1
        echo Remove
        rm concat.h
        echo Swap back
        mv concat-body.h concat.h
        echo Done
    "},
        precompile_step, gen_cmd, args, postcompile_step, problem
    );
    println!("Interestingness test:\n{}", content);
    {
        let mut file = File::create(test_path)?;
        file.write_all(content.as_bytes())?;
    }

    let mut perms = std::fs::metadata(&test_path)?.permissions();
    perms.set_mode(0o700);
    std::fs::set_permissions(&test_path, perms)?;
    Ok(())
}

fn make_compile_step(enabled: bool, file: &str, extra_clang_args: &[&str]) -> String {
    if enabled {
        format!(
            "{} {} -c {}",
            get_clang_path(),
            make_clang_args(&[PathBuf::from(".")], extra_clang_args).join(" "),
            file,
        )
    } else {
        "".into()
    }
}

fn create_rs_file(rs_path: &Path, directives: &[String]) -> Result<(), std::io::Error> {
    announce_progress("Creating Rust input file");
    let mut file = File::create(rs_path)?;
    file.write_all("use autocxx::include_cpp;\ninclude_cpp! (\n".as_bytes())?;
    for directive in directives {
        file.write_all(directive.as_bytes())?;
    }
    file.write_all(");\n".as_bytes())?;
    Ok(())
}

fn create_concatenated_header(headers: &[&str], listing_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Creating preprocessed header");
    let mut file = File::create(listing_path)?;
    for header in headers {
        file.write_all(format!("#include \"{}\"\n", header).as_bytes())?;
    }
    Ok(())
}
<commit_msg>Strip cxx.h during reduction<commit_after>//! Crate to `#include` C++ headers in your Rust code, and generate
//! idiomatic bindings using `cxx`. See [include_cpp] for details.

// Copyright 2020 Google LLC
//
// 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
//
//    https://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.

#[cfg(test)]
mod reduce_test;

use std::{
    fs::File,
    io::Write,
    os::unix::prelude::PermissionsExt,
    path::{Path, PathBuf},
    process::Command,
};

use autocxx_engine::{get_clang_path, make_clang_args, preprocess};
use clap::{crate_authors, crate_version, App, Arg, ArgMatches};
use indoc::indoc;
use itertools::Itertools;
use tempfile::TempDir;

static LONG_HELP: &str = indoc! {"
Command line utility to minimize autocxx bug cases.

This is a wrapper for creduce.

Example command-line:
autocxx-reduce -I my-inc-dir -h my-header -d 'generate!(\"MyClass\")' -k -- --n 64
"};

fn main() {
    let matches = App::new("autocxx-reduce")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Reduce a C++ test case")
        .long_about(LONG_HELP)
        .arg(
            Arg::with_name("inc")
                .short("I")
                .long("inc")
                .multiple(true)
                .number_of_values(1)
                .value_name("INCLUDE DIRS")
                .help("include path")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("define")
                .short("D")
                .long("define")
                .multiple(true)
                .number_of_values(1)
                .value_name("DEFINE")
                .help("macro definition")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("header")
                .short("h")
                .long("header")
                .multiple(true)
                .number_of_values(1)
                .required(true)
                .value_name("HEADER")
                .help("header file name")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("directive")
                .short("d")
                .long("directive")
                .multiple(true)
                .number_of_values(1)
                .value_name("DIRECTIVE")
                .help("directives to put within include_cpp!")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("problem")
                .short("p")
                .long("problem")
                .required(true)
                .value_name("PROBLEM")
                .help("problem string we're looking for... may be in logs, or in generated C++, or generated .rs")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("creduce")
                .long("creduce")
                .required(true)
                .value_name("PATH")
                .help("creduce binary location")
                .default_value("creduce")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("output")
                .short("o")
                .long("output")
                .value_name("OUTPUT")
                .help("where to write minimized output")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("gen-cmd")
                .short("g")
                .long("gen-cmd")
                .value_name("GEN-CMD")
                .help("where to find autocxx-gen")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("keep")
                .short("k")
                .long("keep-dir")
                .help("keep the temporary directory for debugging purposes"),
        )
        .arg(
            Arg::with_name("clang-args")
                .short("c")
                .long("clang-arg")
                .multiple(true)
                .value_name("CLANG_ARG")
                .help("Extra arguments to pass to Clang"),
        )
        .arg(
            Arg::with_name("no-precompile")
                .long("no-precompile")
                .help("Do not precompile the C++ header before passing to autocxxgen"),
        )
        .arg(
            Arg::with_name("no-postcompile")
                .long("no-postcompile")
                .help("Do not post-compile the C++ generated by autocxxgen"),
        )
        .arg(Arg::with_name("creduce-args").last(true).multiple(true))
        .get_matches();
    run(matches).unwrap();
}

fn run(matches: ArgMatches) -> Result<(), std::io::Error> {
    let keep_tmp = matches.is_present("keep");
    let tmp_dir = TempDir::new()?;
    let r = do_run(matches, &tmp_dir);
    if keep_tmp {
        println!(
            "Keeping temp dir created at: {}",
            tmp_dir.into_path().to_str().unwrap()
        );
    }
    r
}

fn do_run(matches: ArgMatches, tmp_dir: &TempDir) -> Result<(), std::io::Error> {
    let incs: Vec<_> = matches
        .values_of("inc")
        .unwrap_or_default()
        .map(PathBuf::from)
        .collect();
    let defs: Vec<_> = matches.values_of("define").unwrap_or_default().collect();
    let headers: Vec<_> = matches.values_of("header").unwrap_or_default().collect();
    let listing_path = tmp_dir.path().join("listing.h");
    create_concatenated_header(&headers, &listing_path)?;
    let concat_path = tmp_dir.path().join("concat.h");
    announce_progress(&format!(
        "Preprocessing {:?} to {:?}",
        listing_path, concat_path
    ));
    preprocess(&listing_path, &concat_path, &incs, &defs)?;
    let rs_path = tmp_dir.path().join("input.rs");
    let directives: Vec<_> = std::iter::once("#include \"concat.h\"\n".to_string())
        .chain(
            matches
                .values_of("directive")
                .unwrap_or_default()
                .map(|s| format!("{}\n", s)),
        )
        .collect();
    create_rs_file(&rs_path, &directives)?;
    let extra_clang_args: Vec<_> = matches
        .values_of("clang-args")
        .unwrap_or_default()
        .collect();
    let default_gen_cmd = std::env::current_exe()?
        .parent()
        .unwrap()
        .join("autocxx-gen")
        .to_str()
        .unwrap()
        .to_string();
    let gen_cmd = matches.value_of("gen-cmd").unwrap_or(&default_gen_cmd);
    run_sample_gen_cmd(gen_cmd, &rs_path, &tmp_dir.path(), &extra_clang_args)?;
    let interestingness_test = tmp_dir.path().join("test.sh");
    create_interestingness_test(
        gen_cmd,
        &interestingness_test,
        matches.value_of("problem").unwrap(),
        &rs_path,
        &extra_clang_args,
        matches.value_of("no-precompile").is_none(),
        matches.value_of("no-postcompile").is_none(),
    )?;
    run_creduce(
        matches.value_of("creduce").unwrap(),
        &interestingness_test,
        &concat_path,
        matches.values_of("creduce-args").unwrap_or_default(),
    );
    announce_progress("creduce completed");
    let output_path = matches.value_of("output");
    match output_path {
        None => print_minimized_case(&concat_path)?,
        Some(output_path) => {
            std::fs::copy(&concat_path, &PathBuf::from(output_path))?;
        }
    };
    Ok(())
}

fn announce_progress(msg: &str) {
    println!("=== {} ===", msg);
}

fn print_minimized_case(concat_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Completed. Minimized test case:");
    let contents = std::fs::read_to_string(concat_path)?;
    println!("{}", contents);
    Ok(())
}

/// Arguments we pass to creduce if supported. This pass always seems to cause a crash
/// as far as I can tell, so always exclude it. It may be environment-dependent,
/// of course, but as I'm the primary user of this tool I am ruthlessly removing it.
const REMOVE_PASS_LINE_MARKERS: &[&str] = &["--remove-pass", "pass_line_markers", "*"];
const SKIP_INITIAL_PASSES: &[&str] = &["--skip-initial-passes"];

fn creduce_supports_remove_pass(creduce_cmd: &str) -> bool {
    let msg = Command::new(creduce_cmd)
        .arg("--help")
        .output()
        .unwrap()
        .stdout;
    let msg = std::str::from_utf8(&msg).unwrap();
    msg.contains("--remove-pass")
}

fn run_creduce<'a>(
    creduce_cmd: &str,
    interestingness_test: &'a Path,
    concat_path: &'a Path,
    creduce_args: impl Iterator<Item = &'a str>,
) {
    announce_progress("creduce");
    let args = std::iter::once(interestingness_test.to_str().unwrap())
        .chain(std::iter::once(concat_path.to_str().unwrap()))
        .chain(creduce_args)
        .chain(
            if creduce_supports_remove_pass(creduce_cmd) {
                REMOVE_PASS_LINE_MARKERS
            } else {
                SKIP_INITIAL_PASSES
            }
            .iter()
            .copied(),
        )
        .collect::<Vec<_>>();
    println!("Command: {} {}", creduce_cmd, args.join(" "));
    Command::new(creduce_cmd)
        .args(args)
        .status()
        .expect("failed to creduce");
}

fn run_sample_gen_cmd(
    gen_cmd: &str,
    rs_file: &Path,
    tmp_dir: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    let args = format_gen_cmd(rs_file, tmp_dir.to_str().unwrap(), extra_clang_args);
    let args = args.collect::<Vec<_>>();
    let args_str = args.join(" ");
    announce_progress(&format!("Running sample gen cmd: {} {}", gen_cmd, args_str));
    Command::new(gen_cmd).args(args).status()?;
    Ok(())
}

fn format_gen_cmd<'a>(
    rs_file: &Path,
    dir: &str,
    extra_clang_args: &'a [&str],
) -> impl Iterator<Item = String> + 'a {
    let args = [
        "-o".to_string(),
        dir.to_string(),
        "-I".to_string(),
        dir.to_string(),
        rs_file.to_str().unwrap().to_string(),
        "--gen-rs-complete".to_string(),
        "--gen-cpp".to_string(),
        "--".to_string(),
    ]
    .to_vec();
    args.into_iter()
        .chain(extra_clang_args.iter().map(|s| s.to_string()))
}

fn create_interestingness_test(
    gen_cmd: &str,
    test_path: &Path,
    problem: &str,
    rs_file: &Path,
    extra_clang_args: &[&str],
    precompile: bool,
    postcompile: bool,
) -> Result<(), std::io::Error> {
    announce_progress("Creating interestingness test");
    // Ensure we refer to the input header by relative path
    // because creduce will invoke us in some other directory with
    // a copy thereof.
    let mut args = format_gen_cmd(rs_file, "$(pwd)", extra_clang_args);
    let args = args.join(" ");
    let precompile_step = make_compile_step(precompile, "concat.h", extra_clang_args);
    // For the compile afterwards, we have to avoid including any system headers.
    // We rely on equivalent content being hermetically inside concat.h.
    let postcompile_step = format!(
        "; mv gen0.cc gen0-orig.cc && sed -e 's/#include <.*>//g' < gen0-orig.cc | sed -e 's/#include \"cxx.h\"//g' > gen0.cc && mv autocxxgen.h autocxxgen-orig.h && sed -e 's/#include <.*>//g' < autocxxgen-orig.h | sed -e 's/#include \"cxx.h\"//g' > autocxxgen.h && {} 2>&1",
        make_compile_step(postcompile, "gen0.cc", extra_clang_args)
    );
    let content = format!(
        indoc! {"
        #!/bin/sh
        set -e
        echo Precompile
        {}
        echo Move
        mv concat.h concat-body.h
        echo Codegen
        (echo \"#ifndef __CONCAT_H__\"; echo \"#define __CONCAT_H__\"; echo '#include \"concat-body.h\"'; echo \"#endif\") > concat.h
        ({} {} 2>&1 && cat gen.complete.rs && cat autocxxgen*.h {}) | grep \"{}\"  >/dev/null 2>&1
        echo Remove
        rm concat.h
        echo Swap back
        mv concat-body.h concat.h
        echo Done
    "},
        precompile_step, gen_cmd, args, postcompile_step, problem
    );
    println!("Interestingness test:\n{}", content);
    {
        let mut file = File::create(test_path)?;
        file.write_all(content.as_bytes())?;
    }

    let mut perms = std::fs::metadata(&test_path)?.permissions();
    perms.set_mode(0o700);
    std::fs::set_permissions(&test_path, perms)?;
    Ok(())
}

fn make_compile_step(enabled: bool, file: &str, extra_clang_args: &[&str]) -> String {
    if enabled {
        format!(
            "{} {} -c {}",
            get_clang_path(),
            make_clang_args(&[PathBuf::from(".")], extra_clang_args).join(" "),
            file,
        )
    } else {
        "".into()
    }
}

fn create_rs_file(rs_path: &Path, directives: &[String]) -> Result<(), std::io::Error> {
    announce_progress("Creating Rust input file");
    let mut file = File::create(rs_path)?;
    file.write_all("use autocxx::include_cpp;\ninclude_cpp! (\n".as_bytes())?;
    for directive in directives {
        file.write_all(directive.as_bytes())?;
    }
    file.write_all(");\n".as_bytes())?;
    Ok(())
}

fn create_concatenated_header(headers: &[&str], listing_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Creating preprocessed header");
    let mut file = File::create(listing_path)?;
    for header in headers {
        file.write_all(format!("#include \"{}\"\n", header).as_bytes())?;
    }
    Ok(())
}
<|endoftext|>


<commit_before>use std::ffi::{CStr, CString};
use std::str;
use std::slice;
use std::marker::PhantomData;

use libc::{c_int, c_char};

use ejdb_sys;

use self::open_mode::OpenMode;
use utils::TCList;
use Result;

pub mod open_mode {
    use ejdb_sys;

    bitflags! {
        flags OpenMode: u32 {
            const JBOREADER  = ejdb_sys::JBOREADER,
            const JBOWRITER  = ejdb_sys::JBOWRITER,
            const JBOCREAT   = ejdb_sys::JBOCREAT,
            const JBOTRUNC   = ejdb_sys::JBOTRUNC,
            const JBONOLCK   = ejdb_sys::JBONOLCK,
            const JBOLCKNB   = ejdb_sys::JBOLCKNB,
            const JBOTSYNC   = ejdb_sys::JBOTSYNC,
        }
    }

    impl Default for OpenMode {
        #[inline]
        fn default() -> OpenMode {
            JBOREADER | JBOWRITER | JBOCREAT
        }
    }
}

#[derive(Debug)]
#[allow(raw_pointer_derive)]
pub struct Database(*mut ejdb_sys::EJDB);

impl Drop for Database {
    fn drop(&mut self) {
        unsafe {
            ejdb_sys::ejdbdel(self.0);
        }
    }
}

#[inline]
fn last_error_code(ejdb: *mut ejdb_sys::EJDB) -> i32 {
    unsafe { ejdb_sys::ejdbecode(ejdb) }
}

fn error_code_msg(code: i32) -> &'static str {
    unsafe {
        let msg = ejdb_sys::ejdberrmsg(code);
        let msg_cstr = CStr::from_ptr(msg);
        str::from_utf8_unchecked(msg_cstr.to_bytes())
    }
}

impl Database {
    pub fn open<P: Into<Vec<u8>>>(path: P, open_mode: OpenMode) -> Result<Database> {
        let ejdb = unsafe { ejdb_sys::ejdbnew() };
        if ejdb.is_null() {
            return Err("cannot create database".into())
        }

        let p = try!(CString::new(path).map_err(|_| "invalid path specified"));
        unsafe {
            if ejdb_sys::ejdbopen(ejdb, p.as_ptr(), open_mode.bits() as c_int) == 0 {
                return Err(error_code_msg(last_error_code(ejdb)).into());
            }
        }
        Ok(Database(ejdb))
    }

    pub fn last_error_msg(&self) -> Option<&'static str> {
        match last_error_code(self.0) {
            0 => None,
            n => Some(error_code_msg(n))
        }
    }

    pub fn last_error<T>(&self, msg: &'static str) -> Result<T> {
        Err(format!("{}: {}", msg, self.last_error_msg().unwrap_or("unknown error")).into())
    }

    pub fn get_collection_names(&self) -> Result<Vec<String>> {
        let list = unsafe { ejdb_sys::ejdbgetcolls(self.0) };
        if list.is_null() {
            return self.last_error("cannot get collection names");
        }

        let list: TCList<ejdb_sys::EJCOLL> = unsafe { TCList::from_ptr(list) };

        Ok(list.iter()
            .map(|c| Collection(c, PhantomData))
            .map(|c| c.name())
            .collect())
    }

    pub fn get_collection<S: Into<Vec<u8>>>(&self, name: S) -> Result<Option<Collection>> {
        let p = try!(CString::new(name).map_err(|_| "invalid collection name"));
        let coll = unsafe { ejdb_sys::ejdbgetcoll(self.0, p.as_ptr()) };
        if coll.is_null() {
            match self.last_error_msg() {
                None => Ok(None),
                Some(msg) => Err(msg.into())
            }
        } else {
            Ok(Some(Collection(coll, PhantomData)))
        }
    }

    pub fn get_or_create_collection<S: Into<Vec<u8>>>(&self, name: S, options: CollectionOptions) -> Result<Collection> {
        let p = try!(CString::new(name).map_err(|_| "invalid collection name"));
        let mut ejcollopts = ejdb_sys::EJCOLLOPTS {
            large: options.large as u8,
            compressed: options.compressed as u8,
            records: options.records,
            cachedrecords: options.cached_records as c_int
        };
        let coll = unsafe { ejdb_sys::ejdbcreatecoll(self.0, p.as_ptr(), &mut ejcollopts) };
        if coll.is_null() {
            self.last_error("cannot create or open a collection")
        } else {
            Ok(Collection(coll, PhantomData))
        }
    }

    pub fn drop_collection<S: Into<Vec<u8>>>(&self, name: S, prune: bool) -> Result<()> {
        let p = try!(CString::new(name).map_err(|_| "invalid collection name"));
        if unsafe { ejdb_sys::ejdbrmcoll(self.0, p.as_ptr(), prune as u8) } != 0 {
            Ok(())
        } else {
            self.last_error("cannot remove a collection")
        }
    }
}

pub struct CollectionOptions {
    pub large: bool,
    pub compressed: bool,
    pub records: i64,
    pub cached_records: i32
}

impl CollectionOptions {
    pub fn large(mut self, large: bool) -> CollectionOptions {
        self.large = large;
        self
    }

    pub fn compressed(mut self, compressed: bool) -> CollectionOptions {
        self.compressed = compressed;
        self
    }

    pub fn records(mut self, records: i64) -> CollectionOptions {
        self.records = records;
        self
    }

    pub fn cached_records(mut self, cached_records: i32) -> CollectionOptions {
        self.cached_records = cached_records;
        self
    }
}

impl Default for CollectionOptions {
    fn default() -> CollectionOptions {
        CollectionOptions {
            large: false,
            compressed: false,
            records: 128_000,
            cached_records: 0
        }
    }
}

pub struct Collection<'a>(*mut ejdb_sys::EJCOLL, PhantomData<&'a Database>);

impl<'a> Collection<'a> {
    pub fn name(&self) -> String {
        fn get_coll_name(coll: *mut ejdb_sys::EJCOLL) -> (*const u8, usize) {
            #[repr(C)]
            struct EjcollInternal {
                cname: *const c_char,
                cnamesz: c_int
            }

            let coll_internal = coll as *const _ as *const EjcollInternal;
            unsafe {
                ((*coll_internal).cname as *const u8, (*coll_internal).cnamesz as usize)
            }
        }

        let (data, size) = get_coll_name(self.0);
        let bytes = unsafe { slice::from_raw_parts(data, size) };
        // XXX: should be safe, but need to check
        unsafe { str::from_utf8_unchecked(bytes).to_owned() }
    }
}
<commit_msg>Added todo note<commit_after>use std::ffi::{CStr, CString};
use std::str;
use std::slice;
use std::marker::PhantomData;

use libc::{c_int, c_char};

use ejdb_sys;

use self::open_mode::OpenMode;
use utils::TCList;
use Result;

pub mod open_mode {
    use ejdb_sys;

    bitflags! {
        flags OpenMode: u32 {
            const JBOREADER  = ejdb_sys::JBOREADER,
            const JBOWRITER  = ejdb_sys::JBOWRITER,
            const JBOCREAT   = ejdb_sys::JBOCREAT,
            const JBOTRUNC   = ejdb_sys::JBOTRUNC,
            const JBONOLCK   = ejdb_sys::JBONOLCK,
            const JBOLCKNB   = ejdb_sys::JBOLCKNB,
            const JBOTSYNC   = ejdb_sys::JBOTSYNC,
        }
    }

    impl Default for OpenMode {
        #[inline]
        fn default() -> OpenMode {
            JBOREADER | JBOWRITER | JBOCREAT
        }
    }
}

#[derive(Debug)]
#[allow(raw_pointer_derive)]
pub struct Database(*mut ejdb_sys::EJDB);

impl Drop for Database {
    fn drop(&mut self) {
        unsafe {
            ejdb_sys::ejdbdel(self.0);
        }
    }
}

#[inline]
fn last_error_code(ejdb: *mut ejdb_sys::EJDB) -> i32 {
    unsafe { ejdb_sys::ejdbecode(ejdb) }
}

fn error_code_msg(code: i32) -> &'static str {
    unsafe {
        let msg = ejdb_sys::ejdberrmsg(code);
        let msg_cstr = CStr::from_ptr(msg);
        str::from_utf8_unchecked(msg_cstr.to_bytes())
    }
}

impl Database {
    pub fn open<P: Into<Vec<u8>>>(path: P, open_mode: OpenMode) -> Result<Database> {
        let ejdb = unsafe { ejdb_sys::ejdbnew() };
        if ejdb.is_null() {
            return Err("cannot create database".into())
        }

        let p = try!(CString::new(path).map_err(|_| "invalid path specified"));
        unsafe {
            if ejdb_sys::ejdbopen(ejdb, p.as_ptr(), open_mode.bits() as c_int) == 0 {
                return Err(error_code_msg(last_error_code(ejdb)).into());
            }
        }
        Ok(Database(ejdb))
    }

    pub fn last_error_msg(&self) -> Option<&'static str> {
        match last_error_code(self.0) {
            0 => None,
            n => Some(error_code_msg(n))
        }
    }

    pub fn last_error<T>(&self, msg: &'static str) -> Result<T> {
        Err(format!("{}: {}", msg, self.last_error_msg().unwrap_or("unknown error")).into())
    }

    pub fn get_collection_names(&self) -> Result<Vec<String>> {
        let list = unsafe { ejdb_sys::ejdbgetcolls(self.0) };
        if list.is_null() {
            return self.last_error("cannot get collection names");
        }

        let list: TCList<ejdb_sys::EJCOLL> = unsafe { TCList::from_ptr(list) };

        Ok(list.iter()
            .map(|c| Collection(c, PhantomData))
            .map(|c| c.name())
            .collect())
    }

    pub fn get_collection<S: Into<Vec<u8>>>(&self, name: S) -> Result<Option<Collection>> {
        let p = try!(CString::new(name).map_err(|_| "invalid collection name"));
        let coll = unsafe { ejdb_sys::ejdbgetcoll(self.0, p.as_ptr()) };
        if coll.is_null() {
            match self.last_error_msg() {
                None => Ok(None),
                Some(msg) => Err(msg.into())
            }
        } else {
            Ok(Some(Collection(coll, PhantomData)))
        }
    }

    pub fn get_or_create_collection<S: Into<Vec<u8>>>(&self, name: S, options: CollectionOptions) -> Result<Collection> {
        let p = try!(CString::new(name).map_err(|_| "invalid collection name"));
        let mut ejcollopts = ejdb_sys::EJCOLLOPTS {
            large: options.large as u8,
            compressed: options.compressed as u8,
            records: options.records,
            cachedrecords: options.cached_records as c_int
        };
        let coll = unsafe { ejdb_sys::ejdbcreatecoll(self.0, p.as_ptr(), &mut ejcollopts) };
        if coll.is_null() {
            self.last_error("cannot create or open a collection")
        } else {
            Ok(Collection(coll, PhantomData))
        }
    }

    pub fn drop_collection<S: Into<Vec<u8>>>(&self, name: S, prune: bool) -> Result<()> {
        let p = try!(CString::new(name).map_err(|_| "invalid collection name"));
        if unsafe { ejdb_sys::ejdbrmcoll(self.0, p.as_ptr(), prune as u8) } != 0 {
            Ok(())
        } else {
            self.last_error("cannot remove a collection")
        }
    }
}

pub struct CollectionOptions {
    pub large: bool,
    pub compressed: bool,
    pub records: i64,
    pub cached_records: i32
}

impl CollectionOptions {
    pub fn large(mut self, large: bool) -> CollectionOptions {
        self.large = large;
        self
    }

    pub fn compressed(mut self, compressed: bool) -> CollectionOptions {
        self.compressed = compressed;
        self
    }

    pub fn records(mut self, records: i64) -> CollectionOptions {
        self.records = records;
        self
    }

    pub fn cached_records(mut self, cached_records: i32) -> CollectionOptions {
        self.cached_records = cached_records;
        self
    }
}

impl Default for CollectionOptions {
    fn default() -> CollectionOptions {
        CollectionOptions {
            large: false,
            compressed: false,
            records: 128_000,
            cached_records: 0
        }
    }
}

pub struct Collection<'a>(*mut ejdb_sys::EJCOLL, PhantomData<&'a Database>);

impl<'a> Collection<'a> {
    // TODO: use ejdbmeta
    pub fn name(&self) -> String {
        fn get_coll_name(coll: *mut ejdb_sys::EJCOLL) -> (*const u8, usize) {
            #[repr(C)]
            struct EjcollInternal {
                cname: *const c_char,
                cnamesz: c_int
            }

            let coll_internal = coll as *const _ as *const EjcollInternal;
            unsafe {
                ((*coll_internal).cname as *const u8, (*coll_internal).cnamesz as usize)
            }
        }

        let (data, size) = get_coll_name(self.0);
        let bytes = unsafe { slice::from_raw_parts(data, size) };
        // XXX: should be safe, but need to check
        unsafe { str::from_utf8_unchecked(bytes).to_owned() }
    }
}
<|endoftext|>


<commit_before>use std::net::IpAddr;
use std::path::PathBuf;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::os::unix::io::RawFd;
use std::ascii::AsciiExt;

use serde::de::{Deserializer, Deserialize, Error as DeError};
use serde::ser::{Serializer, Serialize};
use quire::validate::{Structure, Sequence, Scalar, Numeric, Enum};
use quire::validate::{Mapping, Nothing, Anything};
use id_map::{IdMap, IdMapExt, mapping_validator};

use sandbox_config::SandboxConfig;
use range::{in_range};
use child_config::ChildKind;

pub const DEFAULT_KILL_TIMEOUT: f32 = 5.;

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct TmpfsInfo {
    pub size: usize,
    pub mode: u32,
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct PersistentInfo {
    pub path: PathBuf,
    pub mkdir: bool,
    pub mode: u32,
    pub user: u32,
    pub group: u32,
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct StatedirInfo {
    pub path: PathBuf,
    pub mode: u32,
    pub user: u32,
    pub group: u32,
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub enum Volume {
    Readonly(PathBuf),
    Persistent(PersistentInfo),
    Tmpfs(TmpfsInfo),
    Statedir(StatedirInfo),
}

#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Copy)]
pub enum ContainerKind {
    Daemon,
    Command,
    CommandOrDaemon,
}

impl ContainerKind {
    pub fn matches(self, child_kind: ChildKind) -> bool {
        use container_config::ContainerKind as L;
        use child_config::ChildKind as R;
        match (self, child_kind) {
            (L::Command, R::Command) => true,
            (L::Daemon, R::Daemon) => true,
            (L::CommandOrDaemon, R::Command) => true,
            (L::CommandOrDaemon, R::Daemon) => true,
            (L::Command, R::Daemon) => false,
            (L::Daemon, R::Command) => false,
        }
    }
}

#[derive(Deserialize, Serialize, Clone)]
pub struct ResolvConf {
    pub copy_from_host: bool,
}

#[derive(Deserialize, Serialize, Clone)]
pub struct HostsFile {
    pub copy_from_host: bool,
    pub localhost: Option<bool>,
    pub public_hostname: Option<bool>,
}

#[derive(Clone, Debug)]
pub struct Host(pub IpAddr);

#[derive(Deserialize, Serialize, Clone)]
pub struct TcpPort {
    pub host: Host,
    pub fd: RawFd,
    pub reuse_addr: bool,
    pub reuse_port: bool,
    pub listen_backlog: usize,
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub enum Variable {
    TcpPort,
    Name,
    Choice(Vec<String>),
}

#[derive(Deserialize, Serialize)]
pub struct ContainerConfig {
    pub kind: ContainerKind,
    pub variables: BTreeMap<String, Variable>,
    pub volumes: BTreeMap<String, Volume>,
    pub user_id: u32,
    pub group_id: u32,
    pub restart_timeout: f32,
    pub kill_timeout: f32,
    pub memory_limit: u64,
    pub fileno_limit: u64,
    pub cpu_shares: usize,
    pub executable: String,
    pub arguments: Vec<String>,
    pub environ: BTreeMap<String, String>,
    pub workdir: PathBuf,
    pub resolv_conf: ResolvConf,
    pub hosts_file: HostsFile,
    pub uid_map: Vec<IdMap>,
    pub gid_map: Vec<IdMap>,
    pub stdout_stderr_file: Option<PathBuf>,
    pub interactive: bool,
    pub restart_process_only: bool,
    pub tcp_ports: HashMap<String, TcpPort>,
}

#[derive(Deserialize, Serialize)]
pub struct InstantiatedConfig {
    pub kind: ContainerKind,
    pub volumes: BTreeMap<String, Volume>,
    pub user_id: u32,
    pub group_id: u32,
    pub restart_timeout: f32,
    pub kill_timeout: f32,
    pub memory_limit: u64,
    pub fileno_limit: u64,
    pub cpu_shares: usize,
    pub executable: String,
    pub arguments: Vec<String>,
    pub environ: BTreeMap<String, String>,
    pub workdir: PathBuf,
    pub resolv_conf: ResolvConf,
    pub hosts_file: HostsFile,
    pub uid_map: Vec<IdMap>,
    pub gid_map: Vec<IdMap>,
    pub stdout_stderr_file: Option<PathBuf>,
    pub interactive: bool,
    pub restart_process_only: bool,
    pub tcp_ports: HashMap<u16, TcpPort>,
}

pub struct Variables<'a> {
    pub user_vars: &'a HashMap<String, String>,
    pub lithos_name: &'a str,
    pub lithos_config_filename: &'a str,
}

impl InstantiatedConfig {
    pub fn map_uid(&self, internal_uid: u32) -> Option<u32> {
        self.uid_map.map_id(internal_uid)
    }
    pub fn map_gid(&self, internal_gid: u32) -> Option<u32> {
        self.gid_map.map_id(internal_gid)
    }
}

impl ContainerConfig {
    pub fn validator<'x>() -> Structure<'x> {
        Structure::new()
        .member("kind", Scalar::new().default("Daemon"))
        .member("variables", Mapping::new(
            Scalar::new(),
            Enum::new()
                .option("TcpPort", Nothing)
                .option("Name", Nothing)
                .option("Choice", Sequence::new(Scalar::new()))
        ))
        .member("metadata", Anything)
        .member("volumes", Mapping::new(
                Scalar::new(),
                volume_validator()))
        .member("user_id", Numeric::new())
        .member("group_id", Numeric::new().default(0))
        .member("memory_limit", Numeric::new().default(0x7fffffffffffffffi64))
        .member("fileno_limit", Numeric::new().default(1024))
        .member("cpu_shares", Numeric::new().default(1024))
        .member("restart_timeout", Numeric::new().min(0).max(86400).default(1))
        .member("kill_timeout",
            Numeric::new().min(0).max(86400)
                .default(DEFAULT_KILL_TIMEOUT as i64))
        .member("executable", Scalar::new())
        .member("arguments", Sequence::new(Scalar::new()))
        .member("environ", Mapping::new(
                Scalar::new(),
                Scalar::new()))
        .member("workdir", Scalar::new().default("/"))
        .member("resolv_conf", Structure::new()
            .member("copy_from_host", Scalar::new().default(true)))
        .member("hosts_file", Structure::new()
            .member("copy_from_host", Scalar::new().default(true))
            .member("localhost", Scalar::new().optional())
            .member("public_hostname", Scalar::new().optional()))
        .member("uid_map", mapping_validator())
        .member("gid_map", mapping_validator())
        .member("stdout_stderr_file", Scalar::new().optional())
        .member("interactive", Scalar::new().default(false))
        .member("restart_process_only", Scalar::new().default(false))
        .member("tcp_ports", Mapping::new(
            Scalar::new(),
            Structure::new()
                .member("host", Scalar::new().default("0.0.0.0"))
                .member("fd", Numeric::new().min(0).optional())
                .member("reuse_addr", Scalar::new().default(true))
                .member("reuse_port", Scalar::new().default(false))
                .member("listen_backlog", Scalar::new().default(128))
            ))
    }
    pub fn instantiate(&self, variables: &Variables)
        -> Result<InstantiatedConfig, Vec<String>>
    {
        let mut errors1 = HashSet::new();
        let mut errors2 = HashSet::new();
        let result = {
            let mut replacer = |varname: &str| {
                let val = variables.user_vars.get(varname).map(|x| x.clone())
                    .or_else(|| match varname {
                        "lithos:name"
                        => Some(variables.lithos_name.to_string()),
                        "lithos:config_filename"
                        => Some(variables.lithos_config_filename.to_string()),
                        _ => None,
                    });
                match val {
                    Some(x) => x,
                    None => {
                        errors1.insert(format!("unknown variable {:?}", varname));
                        return format!("<<no var {:?}>>", varname);
                    }
                }
            };
            InstantiatedConfig {
                kind: self.kind.clone(),
                volumes: self.volumes.clone(),
                user_id: self.user_id.clone(),
                group_id: self.group_id.clone(),
                restart_timeout: self.restart_timeout.clone(),
                kill_timeout: self.kill_timeout.clone(),
                memory_limit: self.memory_limit.clone(),
                fileno_limit: self.fileno_limit.clone(),
                cpu_shares: self.cpu_shares.clone(),
                executable: self.executable.clone(),
                arguments: self.arguments.iter()
                    .map(|x| replace_vars(&x, &mut replacer).into())
                    .collect(),
                environ: self.environ.iter()
                    .map(|(key, val)| {
                        (key.clone(),
                         replace_vars(&val, &mut replacer).into())
                    })
                    .collect(),
                workdir: self.workdir.clone(),
                resolv_conf: self.resolv_conf.clone(),
                hosts_file: self.hosts_file.clone(),
                uid_map: self.uid_map.clone(),
                gid_map: self.gid_map.clone(),
                stdout_stderr_file: self.stdout_stderr_file.clone(),
                interactive: self.interactive.clone(),
                restart_process_only: self.restart_process_only.clone(),
                tcp_ports: self.tcp_ports.iter()
                    .map(|(key, val)| {
                        let s = replace_vars(&key, &mut replacer);
                        let port = match s.parse::<u16>() {
                            Ok(x) => x,
                            Err(e) => {
                                errors2.insert(format!("Bad port {:?}: {}",
                                    key, e));
                                return (0, val.clone());
                            }
                        };
                        (port, val.clone())
                    })
                    .collect(),
            }
        };
        if errors1.len() > 0 || errors2.len() > 0 {
            return Err(errors1.into_iter().chain(errors2.into_iter())
                       .collect());
        } else {
            return Ok(result);
        }
    }
}

pub fn volume_validator<'x>() -> Enum<'x> {
    Enum::new()
    .option("Persistent",  Structure::new()
        .member("path",  Scalar::new().default("/"))
        .member("mkdir",  Scalar::new().default(false))
        .member("mode",  Numeric::new().min(0).max(0o1777).default(0o777))
        .member("user",  Numeric::new().default(0))
        .member("group",  Numeric::new().default(0)))
    .option("Readonly", Scalar::new())
    .option("Tmpfs", Structure::new()
        .member("size", Numeric::new().min(0).default(100*1024*1024))
        .member("mode", Numeric::new().min(0).max(0o1777).default(0o777)))
    .option("Statedir", Structure::new()
        .member("path", Scalar::new().default("/"))
        .member("mode", Numeric::new().min(0).max(0o1777).default(0o777))
        .member("user", Numeric::new().default(0))
        .member("group", Numeric::new().default(0)))
}

impl<'a> Deserialize<'a> for Host {
    fn deserialize<D: Deserializer<'a>>(d: D) -> Result<Host, D::Error> {
        String::deserialize(d)?.parse().map(Host)
            .map_err(|x| D::Error::custom(format!("{}", x)))
    }
}

impl Serialize for Host {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        format!("{}", self.0).serialize(s)
    }
}

impl Variable {
    pub fn validate(&self, value: &str, sandbox: &SandboxConfig)
        -> Result<(), String>
    {
        match *self {
            Variable::TcpPort => {
                let port = value.parse::<u16>()
                    .map_err(|e| format!(
                        "invalid TcpPort {:?}: {}", value, e))?;
                if !in_range(&sandbox.allow_tcp_ports, port as u32) {
                    return Err(format!(
                        "TcpPort {:?} is not in allowed range", port));
                }
            }
            Variable::Name => {
                let chars_ok = value.chars().all(|x| {
                    x.is_ascii() && x.is_alphanumeric() || x == '-' || x == '_'
                });
                if !chars_ok {
                    return Err(format!("Value {:?} contains characters that \
                        are invalid for names (alphanumeric, `-` and `_`)",
                        value));
                }
            }
            Variable::Choice(ref choices) => {
                if !choices.iter().any(|x| x == value) {
                    return Err(format!("variable value {:?} \
                        is not one of {:?}", value, choices));
                }
            }
        }
        Ok(())
    }
}

fn replace_vars<F, S>(mut s: &str, mut f: F)
    -> String
    where F: FnMut(&str) -> S,
          S: AsRef<str>,
{
    let mut result = String::with_capacity(s.len());
    while let Some(vpos) = s.find("@{") {
        result.push_str(&s[..vpos]);
        s = &s[vpos..];
        if let Some(vend) = s.find('}') {
            let var = s[..vend].trim();
            result.push_str(f(var).as_ref());
            s = &s[vend+1..];
        } else {
            break;  // unclosed vars are just raw text
        }
    }
    result.push_str(s);
    return result;
}

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

    #[test]
    fn just_var() {
        assert_eq!(replace_vars("@{x}", |_| "1"), "1");
    }

    #[test]
    fn suffix() {
        assert_eq!(replace_vars("xxx@{x}", |_| "1"), "xxx1");
    }

    #[test]
    fn prefix() {
        assert_eq!(replace_vars("@{yy}zzz", |_| "1"), "1zzz");
    }

    #[test]
    fn middle() {
        assert_eq!(replace_vars("aaa@{yy}zzz", |_| "1"), "aaa1zzz");
    }
    #[test]
    fn two_vars() {
        assert_eq!(replace_vars("one @{x} two @{ y } three", |_| "1"),
            "one 1 two 1 three");
    }
}
<commit_msg>Tiny compile option to support wasm32 (just for config checking)<commit_after>use std::net::IpAddr;
use std::path::PathBuf;
use std::collections::{BTreeMap, HashMap, HashSet};
#[cfg(not(target_arch="wasm32"))] use std::os::unix::io::RawFd;
use std::ascii::AsciiExt;

use serde::de::{Deserializer, Deserialize, Error as DeError};
use serde::ser::{Serializer, Serialize};
use quire::validate::{Structure, Sequence, Scalar, Numeric, Enum};
use quire::validate::{Mapping, Nothing, Anything};
use id_map::{IdMap, IdMapExt, mapping_validator};

use sandbox_config::SandboxConfig;
use range::{in_range};
use child_config::ChildKind;

pub const DEFAULT_KILL_TIMEOUT: f32 = 5.;

#[cfg(target_arch="wasm32")] type RawFd = i32;

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct TmpfsInfo {
    pub size: usize,
    pub mode: u32,
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct PersistentInfo {
    pub path: PathBuf,
    pub mkdir: bool,
    pub mode: u32,
    pub user: u32,
    pub group: u32,
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct StatedirInfo {
    pub path: PathBuf,
    pub mode: u32,
    pub user: u32,
    pub group: u32,
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub enum Volume {
    Readonly(PathBuf),
    Persistent(PersistentInfo),
    Tmpfs(TmpfsInfo),
    Statedir(StatedirInfo),
}

#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Copy)]
pub enum ContainerKind {
    Daemon,
    Command,
    CommandOrDaemon,
}

impl ContainerKind {
    pub fn matches(self, child_kind: ChildKind) -> bool {
        use container_config::ContainerKind as L;
        use child_config::ChildKind as R;
        match (self, child_kind) {
            (L::Command, R::Command) => true,
            (L::Daemon, R::Daemon) => true,
            (L::CommandOrDaemon, R::Command) => true,
            (L::CommandOrDaemon, R::Daemon) => true,
            (L::Command, R::Daemon) => false,
            (L::Daemon, R::Command) => false,
        }
    }
}

#[derive(Deserialize, Serialize, Clone)]
pub struct ResolvConf {
    pub copy_from_host: bool,
}

#[derive(Deserialize, Serialize, Clone)]
pub struct HostsFile {
    pub copy_from_host: bool,
    pub localhost: Option<bool>,
    pub public_hostname: Option<bool>,
}

#[derive(Clone, Debug)]
pub struct Host(pub IpAddr);

#[derive(Deserialize, Serialize, Clone)]
pub struct TcpPort {
    pub host: Host,
    pub fd: RawFd,
    pub reuse_addr: bool,
    pub reuse_port: bool,
    pub listen_backlog: usize,
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)]
pub enum Variable {
    TcpPort,
    Name,
    Choice(Vec<String>),
}

#[derive(Deserialize, Serialize)]
pub struct ContainerConfig {
    pub kind: ContainerKind,
    pub variables: BTreeMap<String, Variable>,
    pub volumes: BTreeMap<String, Volume>,
    pub user_id: u32,
    pub group_id: u32,
    pub restart_timeout: f32,
    pub kill_timeout: f32,
    pub memory_limit: u64,
    pub fileno_limit: u64,
    pub cpu_shares: usize,
    pub executable: String,
    pub arguments: Vec<String>,
    pub environ: BTreeMap<String, String>,
    pub workdir: PathBuf,
    pub resolv_conf: ResolvConf,
    pub hosts_file: HostsFile,
    pub uid_map: Vec<IdMap>,
    pub gid_map: Vec<IdMap>,
    pub stdout_stderr_file: Option<PathBuf>,
    pub interactive: bool,
    pub restart_process_only: bool,
    pub tcp_ports: HashMap<String, TcpPort>,
}

#[derive(Deserialize, Serialize)]
pub struct InstantiatedConfig {
    pub kind: ContainerKind,
    pub volumes: BTreeMap<String, Volume>,
    pub user_id: u32,
    pub group_id: u32,
    pub restart_timeout: f32,
    pub kill_timeout: f32,
    pub memory_limit: u64,
    pub fileno_limit: u64,
    pub cpu_shares: usize,
    pub executable: String,
    pub arguments: Vec<String>,
    pub environ: BTreeMap<String, String>,
    pub workdir: PathBuf,
    pub resolv_conf: ResolvConf,
    pub hosts_file: HostsFile,
    pub uid_map: Vec<IdMap>,
    pub gid_map: Vec<IdMap>,
    pub stdout_stderr_file: Option<PathBuf>,
    pub interactive: bool,
    pub restart_process_only: bool,
    pub tcp_ports: HashMap<u16, TcpPort>,
}

pub struct Variables<'a> {
    pub user_vars: &'a HashMap<String, String>,
    pub lithos_name: &'a str,
    pub lithos_config_filename: &'a str,
}

impl InstantiatedConfig {
    pub fn map_uid(&self, internal_uid: u32) -> Option<u32> {
        self.uid_map.map_id(internal_uid)
    }
    pub fn map_gid(&self, internal_gid: u32) -> Option<u32> {
        self.gid_map.map_id(internal_gid)
    }
}

impl ContainerConfig {
    pub fn validator<'x>() -> Structure<'x> {
        Structure::new()
        .member("kind", Scalar::new().default("Daemon"))
        .member("variables", Mapping::new(
            Scalar::new(),
            Enum::new()
                .option("TcpPort", Nothing)
                .option("Name", Nothing)
                .option("Choice", Sequence::new(Scalar::new()))
        ))
        .member("metadata", Anything)
        .member("volumes", Mapping::new(
                Scalar::new(),
                volume_validator()))
        .member("user_id", Numeric::new())
        .member("group_id", Numeric::new().default(0))
        .member("memory_limit", Numeric::new().default(0x7fffffffffffffffi64))
        .member("fileno_limit", Numeric::new().default(1024))
        .member("cpu_shares", Numeric::new().default(1024))
        .member("restart_timeout", Numeric::new().min(0).max(86400).default(1))
        .member("kill_timeout",
            Numeric::new().min(0).max(86400)
                .default(DEFAULT_KILL_TIMEOUT as i64))
        .member("executable", Scalar::new())
        .member("arguments", Sequence::new(Scalar::new()))
        .member("environ", Mapping::new(
                Scalar::new(),
                Scalar::new()))
        .member("workdir", Scalar::new().default("/"))
        .member("resolv_conf", Structure::new()
            .member("copy_from_host", Scalar::new().default(true)))
        .member("hosts_file", Structure::new()
            .member("copy_from_host", Scalar::new().default(true))
            .member("localhost", Scalar::new().optional())
            .member("public_hostname", Scalar::new().optional()))
        .member("uid_map", mapping_validator())
        .member("gid_map", mapping_validator())
        .member("stdout_stderr_file", Scalar::new().optional())
        .member("interactive", Scalar::new().default(false))
        .member("restart_process_only", Scalar::new().default(false))
        .member("tcp_ports", Mapping::new(
            Scalar::new(),
            Structure::new()
                .member("host", Scalar::new().default("0.0.0.0"))
                .member("fd", Numeric::new().min(0).optional())
                .member("reuse_addr", Scalar::new().default(true))
                .member("reuse_port", Scalar::new().default(false))
                .member("listen_backlog", Scalar::new().default(128))
            ))
    }
    pub fn instantiate(&self, variables: &Variables)
        -> Result<InstantiatedConfig, Vec<String>>
    {
        let mut errors1 = HashSet::new();
        let mut errors2 = HashSet::new();
        let result = {
            let mut replacer = |varname: &str| {
                let val = variables.user_vars.get(varname).map(|x| x.clone())
                    .or_else(|| match varname {
                        "lithos:name"
                        => Some(variables.lithos_name.to_string()),
                        "lithos:config_filename"
                        => Some(variables.lithos_config_filename.to_string()),
                        _ => None,
                    });
                match val {
                    Some(x) => x,
                    None => {
                        errors1.insert(format!("unknown variable {:?}", varname));
                        return format!("<<no var {:?}>>", varname);
                    }
                }
            };
            InstantiatedConfig {
                kind: self.kind.clone(),
                volumes: self.volumes.clone(),
                user_id: self.user_id.clone(),
                group_id: self.group_id.clone(),
                restart_timeout: self.restart_timeout.clone(),
                kill_timeout: self.kill_timeout.clone(),
                memory_limit: self.memory_limit.clone(),
                fileno_limit: self.fileno_limit.clone(),
                cpu_shares: self.cpu_shares.clone(),
                executable: self.executable.clone(),
                arguments: self.arguments.iter()
                    .map(|x| replace_vars(&x, &mut replacer).into())
                    .collect(),
                environ: self.environ.iter()
                    .map(|(key, val)| {
                        (key.clone(),
                         replace_vars(&val, &mut replacer).into())
                    })
                    .collect(),
                workdir: self.workdir.clone(),
                resolv_conf: self.resolv_conf.clone(),
                hosts_file: self.hosts_file.clone(),
                uid_map: self.uid_map.clone(),
                gid_map: self.gid_map.clone(),
                stdout_stderr_file: self.stdout_stderr_file.clone(),
                interactive: self.interactive.clone(),
                restart_process_only: self.restart_process_only.clone(),
                tcp_ports: self.tcp_ports.iter()
                    .map(|(key, val)| {
                        let s = replace_vars(&key, &mut replacer);
                        let port = match s.parse::<u16>() {
                            Ok(x) => x,
                            Err(e) => {
                                errors2.insert(format!("Bad port {:?}: {}",
                                    key, e));
                                return (0, val.clone());
                            }
                        };
                        (port, val.clone())
                    })
                    .collect(),
            }
        };
        if errors1.len() > 0 || errors2.len() > 0 {
            return Err(errors1.into_iter().chain(errors2.into_iter())
                       .collect());
        } else {
            return Ok(result);
        }
    }
}

pub fn volume_validator<'x>() -> Enum<'x> {
    Enum::new()
    .option("Persistent",  Structure::new()
        .member("path",  Scalar::new().default("/"))
        .member("mkdir",  Scalar::new().default(false))
        .member("mode",  Numeric::new().min(0).max(0o1777).default(0o777))
        .member("user",  Numeric::new().default(0))
        .member("group",  Numeric::new().default(0)))
    .option("Readonly", Scalar::new())
    .option("Tmpfs", Structure::new()
        .member("size", Numeric::new().min(0).default(100*1024*1024))
        .member("mode", Numeric::new().min(0).max(0o1777).default(0o777)))
    .option("Statedir", Structure::new()
        .member("path", Scalar::new().default("/"))
        .member("mode", Numeric::new().min(0).max(0o1777).default(0o777))
        .member("user", Numeric::new().default(0))
        .member("group", Numeric::new().default(0)))
}

impl<'a> Deserialize<'a> for Host {
    fn deserialize<D: Deserializer<'a>>(d: D) -> Result<Host, D::Error> {
        String::deserialize(d)?.parse().map(Host)
            .map_err(|x| D::Error::custom(format!("{}", x)))
    }
}

impl Serialize for Host {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        format!("{}", self.0).serialize(s)
    }
}

impl Variable {
    pub fn validate(&self, value: &str, sandbox: &SandboxConfig)
        -> Result<(), String>
    {
        match *self {
            Variable::TcpPort => {
                let port = value.parse::<u16>()
                    .map_err(|e| format!(
                        "invalid TcpPort {:?}: {}", value, e))?;
                if !in_range(&sandbox.allow_tcp_ports, port as u32) {
                    return Err(format!(
                        "TcpPort {:?} is not in allowed range", port));
                }
            }
            Variable::Name => {
                let chars_ok = value.chars().all(|x| {
                    x.is_ascii() && x.is_alphanumeric() || x == '-' || x == '_'
                });
                if !chars_ok {
                    return Err(format!("Value {:?} contains characters that \
                        are invalid for names (alphanumeric, `-` and `_`)",
                        value));
                }
            }
            Variable::Choice(ref choices) => {
                if !choices.iter().any(|x| x == value) {
                    return Err(format!("variable value {:?} \
                        is not one of {:?}", value, choices));
                }
            }
        }
        Ok(())
    }
}

fn replace_vars<F, S>(mut s: &str, mut f: F)
    -> String
    where F: FnMut(&str) -> S,
          S: AsRef<str>,
{
    let mut result = String::with_capacity(s.len());
    while let Some(vpos) = s.find("@{") {
        result.push_str(&s[..vpos]);
        s = &s[vpos..];
        if let Some(vend) = s.find('}') {
            let var = s[..vend].trim();
            result.push_str(f(var).as_ref());
            s = &s[vend+1..];
        } else {
            break;  // unclosed vars are just raw text
        }
    }
    result.push_str(s);
    return result;
}

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

    #[test]
    fn just_var() {
        assert_eq!(replace_vars("@{x}", |_| "1"), "1");
    }

    #[test]
    fn suffix() {
        assert_eq!(replace_vars("xxx@{x}", |_| "1"), "xxx1");
    }

    #[test]
    fn prefix() {
        assert_eq!(replace_vars("@{yy}zzz", |_| "1"), "1zzz");
    }

    #[test]
    fn middle() {
        assert_eq!(replace_vars("aaa@{yy}zzz", |_| "1"), "aaa1zzz");
    }
    #[test]
    fn two_vars() {
        assert_eq!(replace_vars("one @{x} two @{ y } three", |_| "1"),
            "one 1 two 1 three");
    }
}
<|endoftext|>


<commit_before>use core::mem;
use core::sync::atomic::AtomicBool;

use alloc::sync::Arc;

use crate::paging::{RmmA, RmmArch};
use crate::syscall::FloatRegisters;

use memoffset::offset_of;
use spin::Once;

/// This must be used by the kernel to ensure that context switches are done atomically
/// Compare and exchange this to true when beginning a context switch on any CPU
/// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch
/// This must be done, as no locks can be held on the stack during switch
pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);

const ST_RESERVED: u128 = 0xFFFF_FFFF_FFFF_0000_0000_0000_0000_0000;

pub const KFX_SIZE: usize = 512;
pub const KFX_ALIGN: usize = 16;

#[derive(Clone, Debug)]
#[repr(C)]
pub struct Context {
    /// EFLAGS register
    eflags: usize,
    /// EBX register
    ebx: usize,
    /// EDI register
    edi: usize,
    /// ESI register
    esi: usize,
    /// Base pointer
    ebp: usize,
    /// Stack pointer
    pub(crate) esp: usize,
    /// FSBASE.
    ///
    /// NOTE: Same fsgsbase behavior as with gsbase.
    pub(crate) fsbase: usize,
    /// GSBASE.
    ///
    /// NOTE: Without fsgsbase, this register will strictly be equal to the register value when
    /// running. With fsgsbase, this is neither saved nor restored upon every syscall (there is no
    /// need to!), and thus it must be re-read from the register before copying this struct.
    pub(crate) gsbase: usize,
}

impl Context {
    pub fn new() -> Context {
        Context {
            eflags: 0,
            ebx: 0,
            edi: 0,
            esi: 0,
            ebp: 0,
            esp: 0,
            fsbase: 0,
            gsbase: 0,
        }
    }

    pub fn set_stack(&mut self, address: usize) {
        self.esp = address;
    }

    pub unsafe fn signal_stack(&mut self, handler: extern fn(usize), sig: u8) {
        self.push_stack(sig as usize);
        self.push_stack(handler as usize);
        self.push_stack(signal_handler_wrapper as usize);
    }

    pub unsafe fn push_stack(&mut self, value: usize) {
        self.esp -= mem::size_of::<usize>();
        *(self.esp as *mut usize) = value;
    }

    pub unsafe fn pop_stack(&mut self) -> usize {
        let value = *(self.esp as *const usize);
        self.esp += mem::size_of::<usize>();
        value
    }
}
impl super::Context {
    pub fn get_fx_regs(&self) -> FloatRegisters {
        let mut regs = unsafe { self.kfx.as_ptr().cast::<FloatRegisters>().read() };
        regs._reserved = 0;
        let mut new_st = regs.st_space;
        for st in &mut new_st {
            // Only allow access to the 80 lowest bits
            *st &= !ST_RESERVED;
        }
        regs.st_space = new_st;
        regs
    }

    pub fn set_fx_regs(&mut self, mut new: FloatRegisters) {
        {
            let old = unsafe { &*(self.kfx.as_ptr().cast::<FloatRegisters>()) };
            new._reserved = old._reserved;
            let old_st = new.st_space;
            let mut new_st = new.st_space;
            for (new_st, old_st) in new_st.iter_mut().zip(&old_st) {
                *new_st &= !ST_RESERVED;
                *new_st |= old_st & ST_RESERVED;
            }
            new.st_space = new_st;

            // Make sure we don't use `old` from now on
        }

        unsafe {
            self.kfx.as_mut_ptr().cast::<FloatRegisters>().write(new);
        }
    }
}

pub static EMPTY_CR3: Once<rmm::PhysicalAddress> = Once::new();

// SAFETY: EMPTY_CR3 must be initialized.
pub unsafe fn empty_cr3() -> rmm::PhysicalAddress {
    debug_assert!(EMPTY_CR3.poll().is_some());
    *EMPTY_CR3.get_unchecked()
}

/// Switch to the next context by restoring its stack and registers
pub unsafe fn switch_to(prev: &mut super::Context, next: &mut super::Context) {
    core::arch::asm!("
        fxsave [{prev_fx}]
        fxrstor [{next_fx}]
        ", prev_fx = in(reg) prev.kfx.as_mut_ptr(),
        next_fx = in(reg) next.kfx.as_ptr(),
    );

    {
        use x86::{bits64::segmentation::*, msr};

        prev.arch.fsbase = msr::rdmsr(msr::IA32_FS_BASE) as usize;
        msr::wrmsr(msr::IA32_FS_BASE, next.arch.fsbase as u64);
        prev.arch.gsbase = msr::rdmsr(msr::IA32_KERNEL_GSBASE) as usize;
        msr::wrmsr(msr::IA32_KERNEL_GSBASE, next.arch.gsbase as u64);
    }

    match next.addr_space {
        // Since Arc is essentially just wraps a pointer, in this case a regular pointer (as
        // opposed to dyn or slice fat pointers), and NonNull optimization exists, map_or will
        // hopefully be optimized down to checking prev and next pointers, as next cannot be null.
        Some(ref next_space) => if prev.addr_space.as_ref().map_or(true, |prev_space| !Arc::ptr_eq(&prev_space, &next_space)) {
            // Suppose we have two sibling threads A and B. A runs on CPU 0 and B on CPU 1. A
            // recently called yield and is now here about to switch back. Meanwhile, B is
            // currently creating a new mapping in their shared address space, for example a
            // message on a channel.
            //
            // Unless we acquire this lock, it may be possible that the TLB will not contain new
            // entries. While this can be caught and corrected in a page fault handler, this is not
            // true when entries are removed from a page table!
            next_space.read().table.utable.make_current();
        }
        None => {
            RmmA::set_table(empty_cr3());
        }
    }
    switch_to_inner(&mut prev.arch, &mut next.arch)
}

// Check disassembly!
#[naked]
unsafe extern "cdecl" fn switch_to_inner(_prev: &mut Context, _next: &mut Context) {
    use Context as Cx;

    core::arch::asm!(
        // As a quick reminder for those who are unfamiliar with the System V ABI (extern "C"):
        //
        // - the current parameters are passed in the registers `edi`, `esi`,
        // - we can modify scratch registers, e.g. rax
        // - we cannot change callee-preserved registers arbitrarily, e.g. ebx, which is why we
        //   store them here in the first place.
        concat!("
        pop eax // Pop return address
        pop ecx // Pop prev
        pop edx // Pop next
        push eax // Push return address

        // Save old registers, and load new ones
        mov [ecx + {off_ebx}], ebx
        mov ebx, [edx + {off_ebx}]

        mov [ecx + {off_edi}], edi
        mov edi, [edx + {off_edi}]

        mov [ecx + {off_esi}], esi
        mov esi, [edx + {off_esi}]

        mov [ecx + {off_ebp}], ebp
        mov ebp, [edx + {off_ebp}]

        mov [ecx + {off_esp}], esp
        mov esp, [edx + {off_esp}]

        // push EFLAGS (can only be modified via stack)
        pushfd
        // pop EFLAGS into `self.eflags`
        pop DWORD PTR [ecx + {off_eflags}]

        // push `next.eflags`
        push DWORD PTR [edx + {off_eflags}]
        // pop into EFLAGS
        popfd

        // When we return, we cannot even guarantee that the return address on the stack, points to
        // the calling function, `context::switch`. Thus, we have to execute this Rust hook by
        // ourselves, which will unlock the contexts before the later switch.

        // Note that switch_finish_hook will be responsible for executing `ret`.
        jmp {switch_hook}

        "),

        off_eflags = const(offset_of!(Cx, eflags)),

        off_ebx = const(offset_of!(Cx, ebx)),
        off_edi = const(offset_of!(Cx, edi)),
        off_esi = const(offset_of!(Cx, esi)),
        off_ebp = const(offset_of!(Cx, ebp)),
        off_esp = const(offset_of!(Cx, esp)),

        switch_hook = sym crate::context::switch_finish_hook,
        options(noreturn),
    );
}
#[allow(dead_code)]
#[repr(packed)]
pub struct SignalHandlerStack {
    esi: usize,
    edi: usize,
    edx: usize,
    ecx: usize,
    eax: usize,
    handler: extern fn(usize),
    sig: usize,
    eip: usize,
}

#[naked]
unsafe extern fn signal_handler_wrapper() {
    #[inline(never)]
    unsafe extern "C" fn inner(stack: &SignalHandlerStack) {
        (stack.handler)(stack.sig);
    }

    // Push scratch registers
    core::arch::asm!(
        "
            push eax
            push ecx
            push edx
            push edi
            push esi

            push esp
            call {inner}
            pop esp

            pop esi
            pop edi
            pop edx
            pop ecx
            pop eax
            add esp, 16
            ret
        ",

        inner = sym inner,
        options(noreturn),
    );
}
<commit_msg>Use registers for switch_to_inner instead of stack<commit_after>use core::mem;
use core::sync::atomic::AtomicBool;

use alloc::sync::Arc;

use crate::paging::{RmmA, RmmArch};
use crate::syscall::FloatRegisters;

use memoffset::offset_of;
use spin::Once;

/// This must be used by the kernel to ensure that context switches are done atomically
/// Compare and exchange this to true when beginning a context switch on any CPU
/// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch
/// This must be done, as no locks can be held on the stack during switch
pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);

const ST_RESERVED: u128 = 0xFFFF_FFFF_FFFF_0000_0000_0000_0000_0000;

pub const KFX_SIZE: usize = 512;
pub const KFX_ALIGN: usize = 16;

#[derive(Clone, Debug)]
#[repr(C)]
pub struct Context {
    /// EFLAGS register
    eflags: usize,
    /// EBX register
    ebx: usize,
    /// EDI register
    edi: usize,
    /// ESI register
    esi: usize,
    /// Base pointer
    ebp: usize,
    /// Stack pointer
    pub(crate) esp: usize,
    /// FSBASE.
    ///
    /// NOTE: Same fsgsbase behavior as with gsbase.
    pub(crate) fsbase: usize,
    /// GSBASE.
    ///
    /// NOTE: Without fsgsbase, this register will strictly be equal to the register value when
    /// running. With fsgsbase, this is neither saved nor restored upon every syscall (there is no
    /// need to!), and thus it must be re-read from the register before copying this struct.
    pub(crate) gsbase: usize,
}

impl Context {
    pub fn new() -> Context {
        Context {
            eflags: 0,
            ebx: 0,
            edi: 0,
            esi: 0,
            ebp: 0,
            esp: 0,
            fsbase: 0,
            gsbase: 0,
        }
    }

    pub fn set_stack(&mut self, address: usize) {
        self.esp = address;
    }

    pub unsafe fn signal_stack(&mut self, handler: extern fn(usize), sig: u8) {
        self.push_stack(sig as usize);
        self.push_stack(handler as usize);
        self.push_stack(signal_handler_wrapper as usize);
    }

    pub unsafe fn push_stack(&mut self, value: usize) {
        self.esp -= mem::size_of::<usize>();
        *(self.esp as *mut usize) = value;
    }

    pub unsafe fn pop_stack(&mut self) -> usize {
        let value = *(self.esp as *const usize);
        self.esp += mem::size_of::<usize>();
        value
    }
}
impl super::Context {
    pub fn get_fx_regs(&self) -> FloatRegisters {
        let mut regs = unsafe { self.kfx.as_ptr().cast::<FloatRegisters>().read() };
        regs._reserved = 0;
        let mut new_st = regs.st_space;
        for st in &mut new_st {
            // Only allow access to the 80 lowest bits
            *st &= !ST_RESERVED;
        }
        regs.st_space = new_st;
        regs
    }

    pub fn set_fx_regs(&mut self, mut new: FloatRegisters) {
        {
            let old = unsafe { &*(self.kfx.as_ptr().cast::<FloatRegisters>()) };
            new._reserved = old._reserved;
            let old_st = new.st_space;
            let mut new_st = new.st_space;
            for (new_st, old_st) in new_st.iter_mut().zip(&old_st) {
                *new_st &= !ST_RESERVED;
                *new_st |= old_st & ST_RESERVED;
            }
            new.st_space = new_st;

            // Make sure we don't use `old` from now on
        }

        unsafe {
            self.kfx.as_mut_ptr().cast::<FloatRegisters>().write(new);
        }
    }
}

pub static EMPTY_CR3: Once<rmm::PhysicalAddress> = Once::new();

// SAFETY: EMPTY_CR3 must be initialized.
pub unsafe fn empty_cr3() -> rmm::PhysicalAddress {
    debug_assert!(EMPTY_CR3.poll().is_some());
    *EMPTY_CR3.get_unchecked()
}

/// Switch to the next context by restoring its stack and registers
pub unsafe fn switch_to(prev: &mut super::Context, next: &mut super::Context) {
    core::arch::asm!("
        fxsave [{prev_fx}]
        fxrstor [{next_fx}]
        ", prev_fx = in(reg) prev.kfx.as_mut_ptr(),
        next_fx = in(reg) next.kfx.as_ptr(),
    );

    {
        use x86::{bits64::segmentation::*, msr};

        prev.arch.fsbase = msr::rdmsr(msr::IA32_FS_BASE) as usize;
        msr::wrmsr(msr::IA32_FS_BASE, next.arch.fsbase as u64);
        prev.arch.gsbase = msr::rdmsr(msr::IA32_KERNEL_GSBASE) as usize;
        msr::wrmsr(msr::IA32_KERNEL_GSBASE, next.arch.gsbase as u64);
    }

    match next.addr_space {
        // Since Arc is essentially just wraps a pointer, in this case a regular pointer (as
        // opposed to dyn or slice fat pointers), and NonNull optimization exists, map_or will
        // hopefully be optimized down to checking prev and next pointers, as next cannot be null.
        Some(ref next_space) => if prev.addr_space.as_ref().map_or(true, |prev_space| !Arc::ptr_eq(&prev_space, &next_space)) {
            // Suppose we have two sibling threads A and B. A runs on CPU 0 and B on CPU 1. A
            // recently called yield and is now here about to switch back. Meanwhile, B is
            // currently creating a new mapping in their shared address space, for example a
            // message on a channel.
            //
            // Unless we acquire this lock, it may be possible that the TLB will not contain new
            // entries. While this can be caught and corrected in a page fault handler, this is not
            // true when entries are removed from a page table!
            next_space.read().table.utable.make_current();
        }
        None => {
            RmmA::set_table(empty_cr3());
        }
    }

    core::arch::asm!(
        "call {inner}",
        inner = sym switch_to_inner,
        in("ecx") &mut prev.arch,
        in("edx") &mut next.arch,
    );
}

// Check disassembly!
#[naked]
unsafe extern "cdecl" fn switch_to_inner() {
    use Context as Cx;

    core::arch::asm!(
        // As a quick reminder for those who are unfamiliar with the System V ABI (extern "C"):
        //
        // - the current parameters are passed in the registers `edi`, `esi`,
        // - we can modify scratch registers, e.g. rax
        // - we cannot change callee-preserved registers arbitrarily, e.g. ebx, which is why we
        //   store them here in the first place.
        concat!("
        // ecx is prev, edx is next

        // Save old registers, and load new ones
        mov [ecx + {off_ebx}], ebx
        mov ebx, [edx + {off_ebx}]

        mov [ecx + {off_edi}], edi
        mov edi, [edx + {off_edi}]

        mov [ecx + {off_esi}], esi
        mov esi, [edx + {off_esi}]

        mov [ecx + {off_ebp}], ebp
        mov ebp, [edx + {off_ebp}]

        mov [ecx + {off_esp}], esp
        mov esp, [edx + {off_esp}]

        // push EFLAGS (can only be modified via stack)
        pushfd
        // pop EFLAGS into `self.eflags`
        pop DWORD PTR [ecx + {off_eflags}]

        // push `next.eflags`
        push DWORD PTR [edx + {off_eflags}]
        // pop into EFLAGS
        popfd

        // When we return, we cannot even guarantee that the return address on the stack, points to
        // the calling function, `context::switch`. Thus, we have to execute this Rust hook by
        // ourselves, which will unlock the contexts before the later switch.

        // Note that switch_finish_hook will be responsible for executing `ret`.
        jmp {switch_hook}

        "),

        off_eflags = const(offset_of!(Cx, eflags)),

        off_ebx = const(offset_of!(Cx, ebx)),
        off_edi = const(offset_of!(Cx, edi)),
        off_esi = const(offset_of!(Cx, esi)),
        off_ebp = const(offset_of!(Cx, ebp)),
        off_esp = const(offset_of!(Cx, esp)),

        switch_hook = sym crate::context::switch_finish_hook,
        options(noreturn),
    );
}
#[allow(dead_code)]
#[repr(packed)]
pub struct SignalHandlerStack {
    esi: usize,
    edi: usize,
    edx: usize,
    ecx: usize,
    eax: usize,
    handler: extern fn(usize),
    sig: usize,
    eip: usize,
}

#[naked]
unsafe extern fn signal_handler_wrapper() {
    #[inline(never)]
    unsafe extern "C" fn inner(stack: &SignalHandlerStack) {
        (stack.handler)(stack.sig);
    }

    // Push scratch registers
    core::arch::asm!(
        "
            push eax
            push ecx
            push edx
            push edi
            push esi

            push esp
            call {inner}
            pop esp

            pop esi
            pop edi
            pop edx
            pop ecx
            pop eax
            add esp, 16
            ret
        ",

        inner = sym inner,
        options(noreturn),
    );
}
<|endoftext|>


<commit_before>// Zinc, the bare metal stack for rust.
// Copyright 2014 Dzmitry "kvark" Malyshau <kvarkus@gmail.com>
//
// 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.

/*!
Universal synchronous asynchronous receiver transmitter (USART).

*/

use core::fmt;
use core::result::Result;
use core::intrinsics::abort;

use drivers::chario::CharIO;
use hal::uart;
use hal::stm32f1::init;

use self::UsartPeripheral::*;

#[path="../../util/wait_for.rs"]
#[macro_use] mod wait_for;

/// Available USART peripherals.
#[allow(missing_docs)]
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum UsartPeripheral {
  Usart1,
  Usart2,
  Usart3,
  Uart4,
  Uart5,
}

/// USART word length.
#[allow(missing_docs)]
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum WordLen {
  WordLen8bits = 0,
  WordLen9bits = 1,
}

/// Stop bits configuration.
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum StopBit {
  /// Single stop bit.
  StopBit1bit       = 0,
  /// A half stop bit.
  StopBit05bits    = 1,
  /// Two stop bits.
  StopBit2bits      = 2,
  /// One and a half stop bits.
  StopBit15bits    = 3,
}

/// Structure describing a USART instance.
#[derive(Clone, Copy)]
pub struct Usart {
  reg: &'static reg::USART,
}

impl Usart {
  /// Create a new USART port.
  pub fn new(peripheral: UsartPeripheral, baudrate: u32, word_len: WordLen,
             parity: uart::Parity, stop_bits: StopBit,
             config: &init::ClockConfig) -> Usart {
    use hal::stm32f1::peripheral_clock::PeripheralClock;
    use hal::stm32f1::peripheral_clock as clock;
    use hal::uart::Parity::*;

    let (reg, clock) = match peripheral {
        Usart1 => (&reg::USART1, PeripheralClock::Apb2(clock::BusApb2::Usart1)),
        Usart2 => (&reg::USART2, PeripheralClock::Apb1(clock::BusApb1::Usart2)),
        Usart3 => (&reg::USART3, PeripheralClock::Apb1(clock::BusApb1::Usart3)),
        Uart4  => (&reg::UART4,  PeripheralClock::Apb1(clock::BusApb1::Uart4)),
        Uart5  => (&reg::UART5,  PeripheralClock::Apb1(clock::BusApb1::Uart5)),
    };

    clock.enable();

    reg.cr1.set_word_length(word_len as usize != 0);
    reg.cr2.set_stop_bits(stop_bits as u16);

    // Standard USART baud rate:
    // Tx/Rx baud = Fck / (8 * (2 - OVER8) * USARTDIV)

    let bus_clock = clock.frequency(config);
    let over8 = reg.cr1.oversample_8bit_enable() as usize;
    let idiv = (bus_clock << 4) / (baudrate << (2 - over8));
    reg.brr.set_fraction(((idiv & 0xF) >> over8) as u16);
    reg.brr.set_mantissa((idiv >> 4) as u16);

    let (pe_on, pe_select) = match parity {
        Disabled => (false, false),
        Even => (true, false),
        Odd => (true, true),
        _ => unsafe { abort() }, // not supported
    };
    reg.cr1.set_parity_control_enable(pe_on);
    reg.cr1.set_parity_selection(pe_select);
    //reg.cr3.set_rts_enable(true);
    //reg.cr3.set_cts_enable(true);
    reg.cr1.set_transmitter_enable(true);
    reg.cr1.set_receiver_enable(true);

    reg.cr1.set_usart_enable(true);

    Usart {
      reg: reg,
    }
  }
}

impl CharIO for Usart {
  fn putc(&self, value: char) {
    wait_for!(self.reg.sr.transmit_data_empty());
    self.reg.dr.set_data(value as u16);
  }
}

impl fmt::Write for Usart {
  fn write_str(&mut self, s: &str) -> fmt::Result {
    use core::str::StrExt;
    for b in s.bytes() {
      wait_for!(self.reg.sr.transmit_data_empty());
      self.reg.dr.set_data(b as u16);
    }
    Result::Ok(())
  }
}

mod reg {
  use volatile_cell::VolatileCell;
  use core::ops::Drop;

  ioregs!(USART = {
    0x00 => reg16 sr {  // status
      0 => error_parity     : ro,
      1 => error_framing    : ro,
      2 => error_noise      : ro,
      3 => error_overrun    : ro,
      4 => idle_line        : ro,
      5 => read_data_not_empty      : ro,
      6 => transmission_complete    : ro,
      7 => transmit_data_empty      : ro,
      8 => lin_break        : ro,
      9 => cts              : ro
    },
    0x04 => reg16 dr {  // data
      8..0 => data : rw,
    },
    0x08 => reg16 brr { // baud rate
      3..0  => fraction : rw,
      15..4 => mantissa : rw,
    },
    0x0C => reg16 cr1 { // control 1
      0 => send_back : rw,
      1 => receiver_wakeup : rw,
      2 => receiver_enable : rw,
      3 => transmitter_enable : rw,
      4 => int_idle_enable : rw,
      5 => int_read_data_not_empty_enable : rw,
      6 => int_transmission_complete_enable : rw,
      7 => int_transmission_data_empty_enable : rw,
      8 => int_pe_enable : rw, // = USART_CR1_PEIE, not sure about it
      9 => parity_selection : rw,
      10 => parity_control_enable : rw,
      11 => wakeup_method : rw,
      12 => word_length : rw,
      13 => usart_enable : rw,
      // 14 => reserved : ro,
      15 => oversample_8bit_enable : rw,
    },
    0x10 => reg16 cr2 { // control 2
      3..0 => address : rw,
      5 => line_break_length : rw,
      6 => int_line_break_enable : rw,
      8 => last_bit_clock_pulse : rw,
      9 => clock_phase : rw,
      10 => clock_polarity : rw,
      11 => clock_enable : rw,
      13..12 => stop_bits : rw,
      14 => line_mode_enable : rw,
    },
    0x14 => reg16 cr3 { // control 3
      0 => int_error_enable : rw,
      1 => irda_mode_enable : rw,
      2 => irda_low_power : rw,
      3 => half_duplex_selection : rw,
      4 => smartcard_nack_enable : rw,
      5 => smartcard_mode_enable : rw,
      6 => dma_receiver_enable : rw,
      7 => dma_transmitter_enable : rw,
      8 => rts_enable : rw,
      9 => cts_enable : rw,
      10 => int_cts_enable : rw,
      11 => one_sample_method_enable : rw,
    },
    0x18 => reg16 gtpr {    // guard time and prescaler
      7..0  => prescaler  : rw,
      15..8 => guard_time : rw,
    },
  });

  extern {
    #[link_name="stm32f1_iomem_USART1"] pub static USART1: USART;
    #[link_name="stm32f1_iomem_USART2"] pub static USART2: USART;
    #[link_name="stm32f1_iomem_USART3"] pub static USART3: USART;
    #[link_name="stm32f1_iomem_UART4"]  pub static UART4:  USART;
    #[link_name="stm32f1_iomem_UART5"]  pub static UART5:  USART;
  }
}
<commit_msg>stm32f1: usart hal driver with proper BR calculation<commit_after>// Zinc, the bare metal stack for rust.
// Copyright 2014 Dzmitry "kvark" Malyshau <kvarkus@gmail.com>
//
// 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.

/*!
Universal synchronous asynchronous receiver transmitter (USART).

*/

use core::fmt;
use core::result::Result;
use core::intrinsics::abort;

use drivers::chario::CharIO;
use hal::uart;
use hal::stm32f1::init;

use self::UsartPeripheral::*;

#[path="../../util/wait_for.rs"]
#[macro_use] mod wait_for;

/// Available USART peripherals.
#[allow(missing_docs)]
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum UsartPeripheral {
  Usart1,
  Usart2,
  Usart3,
  Uart4,
  Uart5,
}

/// USART word length.
#[allow(missing_docs)]
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum WordLen {
  WordLen8bits = 0,
  WordLen9bits = 1,
}

/// Stop bits configuration.
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum StopBit {
  /// Single stop bit.
  StopBit1bit       = 0,
  /// A half stop bit.
  StopBit05bits    = 1,
  /// Two stop bits.
  StopBit2bits      = 2,
  /// One and a half stop bits.
  StopBit15bits    = 3,
}

/// Structure describing a USART instance.
#[derive(Clone, Copy)]
pub struct Usart {
  reg: &'static reg::USART,
}

impl Usart {
  /// Create a new USART port.
  pub fn new(peripheral: UsartPeripheral, baudrate: u32, word_len: WordLen,
             parity: uart::Parity, stop_bits: StopBit,
             config: &init::ClockConfig) -> Usart {
    use hal::stm32f1::peripheral_clock::PeripheralClock;
    use hal::stm32f1::peripheral_clock as clock;
    use hal::uart::Parity::*;

    let (reg, clock) = match peripheral {
        Usart1 => (&reg::USART1, PeripheralClock::Apb2(clock::BusApb2::Usart1)),
        Usart2 => (&reg::USART2, PeripheralClock::Apb1(clock::BusApb1::Usart2)),
        Usart3 => (&reg::USART3, PeripheralClock::Apb1(clock::BusApb1::Usart3)),
        Uart4  => (&reg::UART4,  PeripheralClock::Apb1(clock::BusApb1::Uart4)),
        Uart5  => (&reg::UART5,  PeripheralClock::Apb1(clock::BusApb1::Uart5)),
    };

    clock.enable();

    reg.cr1.set_word_length(word_len as usize != 0);
    reg.cr2.set_stop_bits(stop_bits as u16);

    // Standard USART baud rate:
    // Tx/Rx baud = Fck / (16 * USARTDIV)

    let bus_clock = clock.frequency(config);
    let idiv = bus_clock / baudrate;
    reg.brrr.set_brr(idiv as u16);

    let (pe_on, pe_select) = match parity {
        Disabled => (false, false),
        Even => (true, false),
        Odd => (true, true),
        _ => unsafe { abort() }, // not supported
    };
    reg.cr1.set_parity_control_enable(pe_on);
    reg.cr1.set_parity_selection(pe_select);
    //reg.cr3.set_rts_enable(true);
    //reg.cr3.set_cts_enable(true);
    reg.cr1.set_transmitter_enable(true);
    reg.cr1.set_receiver_enable(true);

    reg.cr1.set_usart_enable(true);

    Usart {
      reg: reg,
    }
  }
}

impl CharIO for Usart {
  fn putc(&self, value: char) {
    wait_for!(self.reg.sr.transmit_data_empty());
    self.reg.dr.set_data(value as u16);
  }
}

impl fmt::Write for Usart {
  fn write_str(&mut self, s: &str) -> fmt::Result {
    use core::str::StrExt;
    for b in s.bytes() {
      wait_for!(self.reg.sr.transmit_data_empty());
      self.reg.dr.set_data(b as u16);
    }
    Result::Ok(())
  }
}

mod reg {
  use volatile_cell::VolatileCell;
  use core::ops::Drop;

  ioregs!(USART = {
    0x00 => reg16 sr {  // status
      0 => error_parity     : ro,
      1 => error_framing    : ro,
      2 => error_noise      : ro,
      3 => error_overrun    : ro,
      4 => idle_line        : ro,
      5 => read_data_not_empty      : ro,
      6 => transmission_complete    : ro,
      7 => transmit_data_empty      : ro,
      8 => lin_break        : ro,
      9 => cts              : ro
    },
    0x04 => reg16 dr {  // data
      8..0 => data : rw,
    },
    0x08 => reg16 brrr { // baud rate
      15..0 => brr : rw,
    },
    0x0C => reg16 cr1 { // control 1
      0 => send_break : rw,
      1 => receiver_enable : rw,
      2 => receiver_wakeup : rw,
      3 => transmitter_enable : rw,
      4 => int_idle_enable : rw,
      5 => int_read_data_not_empty_enable : rw,
      6 => int_transmission_complete_enable : rw,
      7 => int_transmission_data_empty_enable : rw,
      8 => int_pe_enable : rw, // parity error
      9 => parity_selection : rw,
      10 => parity_control_enable : rw,
      11 => wakeup_method : rw,
      12 => word_length : rw,
      13 => usart_enable : rw,
    },
    0x10 => reg16 cr2 { // control 2
      3..0 => address : rw,
      5 => line_break_length : rw,
      6 => int_line_break_enable : rw,
      8 => last_bit_clock_pulse : rw,
      9 => clock_phase : rw,
      10 => clock_polarity : rw,
      11 => clock_enable : rw,
      13..12 => stop_bits : rw,
      14 => line_mode_enable : rw,
    },
    0x14 => reg16 cr3 { // control 3
      0 => int_error_enable : rw,
      1 => irda_mode_enable : rw,
      2 => irda_low_power : rw,
      3 => half_duplex_selection : rw,
      4 => smartcard_nack_enable : rw,
      5 => smartcard_mode_enable : rw,
      6 => dma_receiver_enable : rw,
      7 => dma_transmitter_enable : rw,
      8 => rts_enable : rw,
      9 => cts_enable : rw,
      10 => int_cts_enable : rw,
    },
    0x18 => reg16 gtpr {    // guard time and prescaler
      7..0  => prescaler  : rw,
      15..8 => guard_time : rw,
    },
  });

  extern {
    #[link_name="stm32f1_iomem_USART1"] pub static USART1: USART;
    #[link_name="stm32f1_iomem_USART2"] pub static USART2: USART;
    #[link_name="stm32f1_iomem_USART3"] pub static USART3: USART;
    #[link_name="stm32f1_iomem_UART4"]  pub static UART4:  USART;
    #[link_name="stm32f1_iomem_UART5"]  pub static UART5:  USART;
  }
}
<|endoftext|>