<commit_before>use toml::Value;

/// Check whether the configuration is valid for the store
///
/// The passed `Value` _must be_ the `[store]` sub-tree of the configuration. Otherwise this will
/// fail.
///
/// It checks whether the configuration looks like the store wants it to be:
///
/// ```toml
/// [store]
/// pre-create-hook-aspects = [ "misc", "encryption", "version-control"]
///
/// [store.aspects.misc]
/// parallel = true
///
/// [store.aspects.encryption]
/// parallel = false
///
/// [store.aspects.version-control]
/// parallel = false
///
/// [store.hooks.gnupg]
/// aspect = "encryption"
/// key = "0x123456789"
///
/// [store.hooks.git]
/// aspect = "version-control"
/// ```
///
/// It checks:
///  * Whether all the maps are there (whether store, store.aspects, store.aspects.example are all
///  maps)
///  * Whether each aspect configuration has a "parallel = <Boolean>" setting
///  * Whether each hook congfiguration has a "aspect = <String>" setting
///
/// It does NOT check:
///  * Whether all aspects which are used in the hook configuration are also configured
///
/// No configuration is a valid configuration, as the store will use the most conservative settings
/// automatically. This has also performance impact, as all hooks run in no-parallel mode then.
/// You have been warned!
///
///
pub fn config_is_valid(config: &Option<Value>) -> bool {
    use std::collections::BTreeMap;
    use std::io::Write;
    use std::io::stderr;

    if config.is_none() {
        return true;
    }

    fn has_key_with_string_ary(v: &BTreeMap<String, Value>, key: &str) -> bool {
        v.get(key)
            .map_or_else(|| {
                write!(stderr(), "Required key '{}' is not in store config", key).ok();
                false
            }, |t| match *t {
                    Value::Array(ref a) => a.iter().all(|elem| {
                        match *elem {
                            Value::String(_) => true,
                            _ => false,
                        }
                    }),
                    _ => {
                        write!(stderr(), "Key '{}' in store config should contain an array", key)
                            .ok();
                        false
                    }
            })
    }

    /// Check that
    /// * the top-level configuration
    /// * is a table
    /// * where all entries of a key `section` (eg. "hooks" or "aspects")
    ///     * Are maps
    ///     * where each has a key `key` (eg. "aspect" or "parallel")
    ///     * which fullfills constraint `f` (typecheck)
    fn check_all_inner_maps_have_key_with<F>(store_config: &BTreeMap<String, Value>,
                                             section: &str,
                                             key: &str,
                                             f: F)
        -> bool
        where F: Fn(&Value) -> bool
    {
        store_config.get(section) // The store config has the section `section`
            .map_or_else(|| {
                write!(stderr(), "Store config expects section '{}' to be present, but isn't.",
                        section).ok();
                false
            }, |section_table| {
                match *section_table { // which is
                    Value::Table(ref section_table) => // a table
                        section_table
                            .iter() // which has values,
                            .all(|(inner_key, cfg)| { // and all of these values
                                match *cfg {
                                    Value::Table(ref hook_config) => { // are tables
                                        hook_config.get(key) // with a key
                                            // fullfilling this constraint
                                            .map_or(false, |hook_aspect| f(&hook_aspect))
                                    },
                                    _ => {
                                        write!(stderr(), "Store config expects '{}' to be in '{}.{}', but isn't.",
                                                 key, section, inner_key).ok();
                                        false
                                    }
                                }
                            }),
                    _ => {
                        write!(stderr(), "Store config expects '{}' to be a Table, but isn't.",
                               section).ok();
                        false
                    }
                }
            })
    }

    match *config {
        Some(Value::Table(ref t)) => {
            has_key_with_string_ary(t, "store-unload-hook-aspects")    &&

            has_key_with_string_ary(t, "pre-create-hook-aspects")      &&
            has_key_with_string_ary(t, "post-create-hook-aspects")     &&
            has_key_with_string_ary(t, "pre-retrieve-hook-aspects")    &&
            has_key_with_string_ary(t, "post-retrieve-hook-aspects")   &&
            has_key_with_string_ary(t, "pre-update-hook-aspects")      &&
            has_key_with_string_ary(t, "post-update-hook-aspects")     &&
            has_key_with_string_ary(t, "pre-delete-hook-aspects")      &&
            has_key_with_string_ary(t, "post-delete-hook-aspects")     &&

            // The section "hooks" has maps which have a key "aspect" which has a value of type
            // String
            check_all_inner_maps_have_key_with(t, "hooks", "aspect",
                                               |asp| is_match!(asp, &Value::String(_))) &&

            // The section "aspects" has maps which have a key "parllel" which has a value of type
            // Boolean
            check_all_inner_maps_have_key_with(t, "aspects", "parallel",
                                               |asp| is_match!(asp, &Value::Boolean(_)))
        }
        _ => {
            write!(stderr(), "Store config is no table").ok();
            false
        },
    }
}

pub fn get_store_unload_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("store-unload-hook-aspects", value)
}

pub fn get_pre_create_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-create-hook-aspects", value)
}

pub fn get_post_create_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-create-hook-aspects", value)
}

pub fn get_pre_retrieve_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-retrieve-hook-aspects", value)
}

pub fn get_post_retrieve_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-retrieve-hook-aspects", value)
}

pub fn get_pre_update_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-update-hook-aspects", value)
}

pub fn get_post_update_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-update-hook-aspects", value)
}

pub fn get_pre_delete_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-delete-hook-aspects", value)
}

pub fn get_post_delete_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-delete-hook-aspects", value)
}

pub fn get_pre_move_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-move-hook-aspects", value)
}

pub fn get_post_move_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-move-hook-aspects", value)
}

#[derive(Debug)]
pub struct AspectConfig {
    parallel: bool,
    mutable_hooks: bool,
    config: Value,
}

impl AspectConfig {

    pub fn new(init: Value) -> AspectConfig {
        debug!("Trying to parse AspectConfig from: {:?}", init);
        let parallel = AspectConfig::is_parallel(&init);
        let muthooks = AspectConfig::allows_mutable_hooks(&init);
        AspectConfig {
            config: init,
            mutable_hooks: muthooks,
            parallel: parallel,
        }
    }

    fn is_parallel(init: &Value) -> bool {
        match *init {
            Value::Table(ref t) =>
                t.get("parallel")
                    .map_or(false, |value| {
                        match *value {
                            Value::Boolean(b) => b,
                            _ => false,
                        }
                    }),
            _ => false,
        }
    }

    fn allows_mutable_hooks(init: &Value) -> bool {
        match *init {
            Value::Table(ref t) =>
                t.get("mutable_hooks")
                    .map_or(false, |value| {
                        match *value {
                            Value::Boolean(b) => b,
                            _ => false,
                        }
                    }),
            _ => false,
        }
    }

    pub fn allow_mutable_hooks(&self) -> bool {
        self.mutable_hooks
    }

    /// Get the aspect configuration for an aspect.
    ///
    /// Pass the store configuration object, this searches in `[aspects][<aspect_name>]`.
    ///
    /// Returns `None` if one of the keys in the chain is not available
    pub fn get_for(v: &Option<Value>, a_name: String) -> Option<AspectConfig> {
        debug!("Get aspect configuration for {:?} from {:?}", a_name, v);
        let res = match *v {
            Some(Value::Table(ref tabl)) => {
                match tabl.get("aspects") {
                    Some(&Value::Table(ref tabl)) => {
                        tabl.get(&a_name[..]).map(|asp| AspectConfig::new(asp.clone()))
                    },

                    _ => None,
                }
            },
            _ => None,
        };
        debug!("Found aspect configuration for {:?}: {:?}", a_name, res);
        res
    }

}

fn get_aspect_names_for_aspect_position(config_name: &'static str, value: &Option<Value>) -> Vec<String> {
    let mut v = vec![];

    match *value {
        Some(Value::Table(ref t)) => {
            match t.get(config_name) {
                Some(&Value::Array(ref a)) => {
                    for elem in a {
                        match *elem {
                            Value::String(ref s) => v.push(s.clone()),
                            _ => warn!("Non-String in configuration, inside '{}'", config_name),
                        }
                    }
                },
                _ => warn!("'{}' configuration key should contain Array, does not", config_name),
            };
        },
        None => warn!("No store configuration, cannot get '{}'", config_name),
        _ => warn!("Configuration is not a table"),
    }
    v
}

<commit_msg>Add helper to check config for implicit-create<commit_after>use toml::Value;

/// Check whether the configuration is valid for the store
///
/// The passed `Value` _must be_ the `[store]` sub-tree of the configuration. Otherwise this will
/// fail.
///
/// It checks whether the configuration looks like the store wants it to be:
///
/// ```toml
/// [store]
/// pre-create-hook-aspects = [ "misc", "encryption", "version-control"]
///
/// [store.aspects.misc]
/// parallel = true
///
/// [store.aspects.encryption]
/// parallel = false
///
/// [store.aspects.version-control]
/// parallel = false
///
/// [store.hooks.gnupg]
/// aspect = "encryption"
/// key = "0x123456789"
///
/// [store.hooks.git]
/// aspect = "version-control"
/// ```
///
/// It checks:
///  * Whether all the maps are there (whether store, store.aspects, store.aspects.example are all
///  maps)
///  * Whether each aspect configuration has a "parallel = <Boolean>" setting
///  * Whether each hook congfiguration has a "aspect = <String>" setting
///
/// It does NOT check:
///  * Whether all aspects which are used in the hook configuration are also configured
///
/// No configuration is a valid configuration, as the store will use the most conservative settings
/// automatically. This has also performance impact, as all hooks run in no-parallel mode then.
/// You have been warned!
///
///
pub fn config_is_valid(config: &Option<Value>) -> bool {
    use std::collections::BTreeMap;
    use std::io::Write;
    use std::io::stderr;

    if config.is_none() {
        return true;
    }

    fn has_key_with_string_ary(v: &BTreeMap<String, Value>, key: &str) -> bool {
        v.get(key)
            .map_or_else(|| {
                write!(stderr(), "Required key '{}' is not in store config", key).ok();
                false
            }, |t| match *t {
                    Value::Array(ref a) => a.iter().all(|elem| {
                        match *elem {
                            Value::String(_) => true,
                            _ => false,
                        }
                    }),
                    _ => {
                        write!(stderr(), "Key '{}' in store config should contain an array", key)
                            .ok();
                        false
                    }
            })
    }

    /// Check that
    /// * the top-level configuration
    /// * is a table
    /// * where all entries of a key `section` (eg. "hooks" or "aspects")
    ///     * Are maps
    ///     * where each has a key `key` (eg. "aspect" or "parallel")
    ///     * which fullfills constraint `f` (typecheck)
    fn check_all_inner_maps_have_key_with<F>(store_config: &BTreeMap<String, Value>,
                                             section: &str,
                                             key: &str,
                                             f: F)
        -> bool
        where F: Fn(&Value) -> bool
    {
        store_config.get(section) // The store config has the section `section`
            .map_or_else(|| {
                write!(stderr(), "Store config expects section '{}' to be present, but isn't.",
                        section).ok();
                false
            }, |section_table| {
                match *section_table { // which is
                    Value::Table(ref section_table) => // a table
                        section_table
                            .iter() // which has values,
                            .all(|(inner_key, cfg)| { // and all of these values
                                match *cfg {
                                    Value::Table(ref hook_config) => { // are tables
                                        hook_config.get(key) // with a key
                                            // fullfilling this constraint
                                            .map_or(false, |hook_aspect| f(&hook_aspect))
                                    },
                                    _ => {
                                        write!(stderr(), "Store config expects '{}' to be in '{}.{}', but isn't.",
                                                 key, section, inner_key).ok();
                                        false
                                    }
                                }
                            }),
                    _ => {
                        write!(stderr(), "Store config expects '{}' to be a Table, but isn't.",
                               section).ok();
                        false
                    }
                }
            })
    }

    match *config {
        Some(Value::Table(ref t)) => {
            has_key_with_string_ary(t, "store-unload-hook-aspects")    &&

            has_key_with_string_ary(t, "pre-create-hook-aspects")      &&
            has_key_with_string_ary(t, "post-create-hook-aspects")     &&
            has_key_with_string_ary(t, "pre-retrieve-hook-aspects")    &&
            has_key_with_string_ary(t, "post-retrieve-hook-aspects")   &&
            has_key_with_string_ary(t, "pre-update-hook-aspects")      &&
            has_key_with_string_ary(t, "post-update-hook-aspects")     &&
            has_key_with_string_ary(t, "pre-delete-hook-aspects")      &&
            has_key_with_string_ary(t, "post-delete-hook-aspects")     &&

            // The section "hooks" has maps which have a key "aspect" which has a value of type
            // String
            check_all_inner_maps_have_key_with(t, "hooks", "aspect",
                                               |asp| is_match!(asp, &Value::String(_))) &&

            // The section "aspects" has maps which have a key "parllel" which has a value of type
            // Boolean
            check_all_inner_maps_have_key_with(t, "aspects", "parallel",
                                               |asp| is_match!(asp, &Value::Boolean(_)))
        }
        _ => {
            write!(stderr(), "Store config is no table").ok();
            false
        },
    }
}

/// Checks whether the store configuration has a key "implicit-create" which maps to a boolean
/// value. If that key is present, the boolean is returned, otherwise false is returned.
pub fn config_implicit_store_create_allowed(config: Option<&Value>) -> bool {
    config.map(|t| {
        match *t {
            Value::Table(ref t) => {
                match t.get("implicit-create") {
                    Some(&Value::Boolean(b)) => b,
                    Some(_) => {
                        warn!("Key 'implicit-create' does not contain a Boolean value");
                        false
                    }
                    None => {
                        warn!("Key 'implicit-create' in store configuration missing");
                        false
                    },
                }
            }
            _ => {
                warn!("Store configuration seems to be no Table");
                false
            },
        }
    }).unwrap_or(false)
}

pub fn get_store_unload_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("store-unload-hook-aspects", value)
}

pub fn get_pre_create_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-create-hook-aspects", value)
}

pub fn get_post_create_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-create-hook-aspects", value)
}

pub fn get_pre_retrieve_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-retrieve-hook-aspects", value)
}

pub fn get_post_retrieve_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-retrieve-hook-aspects", value)
}

pub fn get_pre_update_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-update-hook-aspects", value)
}

pub fn get_post_update_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-update-hook-aspects", value)
}

pub fn get_pre_delete_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-delete-hook-aspects", value)
}

pub fn get_post_delete_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-delete-hook-aspects", value)
}

pub fn get_pre_move_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-move-hook-aspects", value)
}

pub fn get_post_move_aspect_names(value: &Option<Value>) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-move-hook-aspects", value)
}

#[derive(Debug)]
pub struct AspectConfig {
    parallel: bool,
    mutable_hooks: bool,
    config: Value,
}

impl AspectConfig {

    pub fn new(init: Value) -> AspectConfig {
        debug!("Trying to parse AspectConfig from: {:?}", init);
        let parallel = AspectConfig::is_parallel(&init);
        let muthooks = AspectConfig::allows_mutable_hooks(&init);
        AspectConfig {
            config: init,
            mutable_hooks: muthooks,
            parallel: parallel,
        }
    }

    fn is_parallel(init: &Value) -> bool {
        match *init {
            Value::Table(ref t) =>
                t.get("parallel")
                    .map_or(false, |value| {
                        match *value {
                            Value::Boolean(b) => b,
                            _ => false,
                        }
                    }),
            _ => false,
        }
    }

    fn allows_mutable_hooks(init: &Value) -> bool {
        match *init {
            Value::Table(ref t) =>
                t.get("mutable_hooks")
                    .map_or(false, |value| {
                        match *value {
                            Value::Boolean(b) => b,
                            _ => false,
                        }
                    }),
            _ => false,
        }
    }

    pub fn allow_mutable_hooks(&self) -> bool {
        self.mutable_hooks
    }

    /// Get the aspect configuration for an aspect.
    ///
    /// Pass the store configuration object, this searches in `[aspects][<aspect_name>]`.
    ///
    /// Returns `None` if one of the keys in the chain is not available
    pub fn get_for(v: &Option<Value>, a_name: String) -> Option<AspectConfig> {
        debug!("Get aspect configuration for {:?} from {:?}", a_name, v);
        let res = match *v {
            Some(Value::Table(ref tabl)) => {
                match tabl.get("aspects") {
                    Some(&Value::Table(ref tabl)) => {
                        tabl.get(&a_name[..]).map(|asp| AspectConfig::new(asp.clone()))
                    },

                    _ => None,
                }
            },
            _ => None,
        };
        debug!("Found aspect configuration for {:?}: {:?}", a_name, res);
        res
    }

}

fn get_aspect_names_for_aspect_position(config_name: &'static str, value: &Option<Value>) -> Vec<String> {
    let mut v = vec![];

    match *value {
        Some(Value::Table(ref t)) => {
            match t.get(config_name) {
                Some(&Value::Array(ref a)) => {
                    for elem in a {
                        match *elem {
                            Value::String(ref s) => v.push(s.clone()),
                            _ => warn!("Non-String in configuration, inside '{}'", config_name),
                        }
                    }
                },
                _ => warn!("'{}' configuration key should contain Array, does not", config_name),
            };
        },
        None => warn!("No store configuration, cannot get '{}'", config_name),
        _ => warn!("Configuration is not a table"),
    }
    v
}

<|endoftext|>


<commit_before>#![feature(convert)]
#![feature(io)]

extern crate rustc_serialize;
extern crate docopt;
extern crate users;
extern crate glob;
extern crate tempfile;
extern crate libc;
extern crate cronparse;

use cronparse::{CrontabFile, CrontabFileError};
use cronparse::crontab::UserCrontabEntry;
use tempfile::NamedTempFile;
use docopt::Docopt;
use libc::{uid_t, gid_t};
use users::User;
use std::env;
use std::fs;
use std::io::{stdin, stdout, stderr, Write, Read, self};
use std::fs::File;
use std::os::unix::fs::PermissionsExt;
use std::path::{PathBuf, Path};
use std::process::{Command, exit};
use std::ops::Deref;
use std::ffi::CString;

include!(concat!(env!("OUT_DIR"), "/config.rs"));

extern "C" {
    fn chown(path: *const libc::c_char, owner: libc::uid_t, group: libc::gid_t) -> libc::c_int;
}

fn change_owner<P: AsRef<Path>>(path: P, owner: libc::uid_t, group: libc::gid_t) -> Result<(), io::Error> {
    match unsafe { chown(CString::new(path.as_ref().as_os_str().to_bytes().unwrap()).unwrap().as_ptr(), owner, group) } {
        0 => Ok(()),
        -1 => Err(io::Error::last_os_error()),
        _ => unreachable!()
    }
}

static USAGE: &'static str = r#"
Usage: crontab [-u <user>] -l
       crontab [-u <user>] -e [<file>]
       crontab [-u <user>] -s
       crontab [-u <user>] -r [-i]
       crontab -h | --help

Maintain crontab files for individual users

Options:

  -h, --help                Show this help message and exit.
  -u <user>, --user <user>  It specifies the name of the user whose crontab is to
                            be tweaked. If this option is not given, crontab
                            examines "your" crontab, i.e., the crontab of the
                            person executing the command. Note that su(8) can
                            confuse crontab and that if you are running inside of
                            su(8) you should always use the -u option for safety's
                            sake. The first form of this command is used to
                            install a new crontab from some named file or standard
                            input if the pseudo-filename "-" is given.
  -l, --list                The current crontab will be displayed on standard
                            output.
  -r, --remove              The current crontab will be removed.
  -e, --edit                This option is used to edit the current crontab using
                            the editor specified by the VISUAL or EDITOR
                            environment variables. After you exit from the editor,
                            the modified crontab will be installed automatically.
  -s, --show                Show all users who have a crontab.
  -i, --ask                 This option modifies the -r option to prompt the user
                            for a 'y/Y' response before actually removing the
                            crontab.
"#;

#[derive(Debug, RustcDecodable)]
#[allow(non_snake_case)]
struct Args {
    arg_file: Option<String>,
    flag_user: Option<String>,
    flag_list: bool,
    flag_remove: bool,
    flag_edit: bool,
    flag_show: bool,
    flag_ask: bool
}

fn get_editor() -> Option<String> {
    env::var("EDITOR").ok()
        .or_else(|| env::var("VISUAL").ok())
        .or_else(|| ["/usr/bin/editor", "/usr/bin/vim", "/usr/bin/nano", "/usr/bin/mcedit"].iter()
                 .find(|editor| fs::metadata(editor).map(|meta| meta.is_file() && meta.permissions().mode() & 0o0111 != 0).unwrap_or(false))
                 .map(|&s| s.to_owned()))
}

fn confirm(msg: &str) -> bool {
    let mut stdin = stdin();
    let mut stdout = stdout();
    loop {
        stdout.write_all(msg.as_bytes()).unwrap();
        stdout.flush().unwrap();
        let mut buf = [0u8; 1024];
        match stdin.read(&mut buf) {
            Ok(n) if n > 0 && (buf[0] == 121 || buf[0] == 89) => return true,
            Ok(n) if n > 0 && (buf[0] == 110 || buf[0] == 78) => return false,
            _ => { stdout.write_all("Please reply \"y\" or \"n\"\n".as_bytes()).unwrap(); },
        }
    }
}

fn list(cron_file: &Path, cron_user: &User, args: &Args) -> i32 {
    if let Err(e) = File::open(cron_file).map(|file| file.tee(stdout()).bytes().count()) {
        use std::io::ErrorKind::*;
        match e.kind() {
            NotFound => println!("no crontab for {}", cron_user.name),
            _ => println!("failed to read {}: {}", cron_file.display(), e),
        }
        return 1;
    }
    0
}

fn remove(cron_file: &Path, cron_user: &User, args: &Args) -> i32 {
    let mut stderr = stderr();

    if !args.flag_ask || confirm(&*format!("Are you sure you want to delete {} (y/n)? ", cron_file.display())) {
        if let Err(e) = fs::remove_file(cron_file) {
            use std::io::ErrorKind::*;
            match e.kind() {
                NotFound => writeln!(stderr, "no crontab for {}", cron_user.name),
                _ => writeln!(stderr, "failed to remove {}: {}", cron_file.display(), e)
            }.unwrap();
            return 1;
        }
    }
    0
}

fn show(_cron_file: &Path, _cron_user: &User, _args: &Args) -> i32 {
    let mut stderr = stderr();

    if let Ok(dir) = fs::read_dir(USERS_CRONTAB_DIR) {
        for item in dir {
            if let Ok(entry) = item {
                if let Some(name) = entry.file_name().to_str() {
                    if users::get_user_by_name(name).is_some() {
                        println!("{}", name);
                    } else {
                        writeln!(stderr, "WARNING: crontab found with no matching user: {}", name).unwrap();
                    }
                }
            }
        }
    }
    0
}

fn edit(cron_file: &Path, cron_user: &User, _args: &Args) -> i32 {
    use std::io::ErrorKind::*;

    let mut stderr = stderr();

    let editor = match get_editor() {
        None => {
            writeln!(stderr, "no editor found").unwrap();
            return 1;
        },
        Some(editor) => editor
    };

    let mut tmpfile = NamedTempFile::new_in(USERS_CRONTAB_DIR).unwrap();

    if let Err(e) = File::open(cron_file).map(|file| file.tee(&mut tmpfile).bytes().count()) {
        match e.kind() {
            NotFound => tmpfile.write_all("# min hour dom month dow command".as_bytes()).unwrap(),
            _ => {
                writeln!(stderr, "error copying crontab file {}: {}", cron_file.display(), e).unwrap();
                return 1;
            }
        }
    }

    tmpfile.flush().unwrap();
    {
        change_owner(tmpfile.path(), cron_user.uid, cron_user.primary_group).unwrap();
        let _guard = users::switch_user_group(cron_user.uid, cron_user.primary_group);
        match Command::new(editor).arg(tmpfile.path()).status() {
            Ok(status) if status.success() => (),
            _ => {
                writeln!(stderr, "edit aborted, your edit is kept here: {}", tmpfile.path().display()).unwrap();
                return 1;
            }
        }
    }

    if let Err(err) = check_crontab_syntax(tmpfile.path()) {
        writeln!(stderr, "syntax error in new crontab file: {}", err).unwrap();
        return 1;
    }

    if let Err(err) = tmpfile.persist(cron_file) {
        writeln!(stderr, "unexpected error: {}, your edit is kept here: {}", err.error, err.file.path().display()).unwrap();
        return 1;
    }

    0
}

fn replace(cron_file: &Path, cron_user: &User, args: &Args) -> i32 {
    let mut stderr = stderr();
    let mut tmpfile = NamedTempFile::new_in(USERS_CRONTAB_DIR).unwrap();

    match args.arg_file {
        Some(ref name) if &**name == "-" => { stdin().tee(&mut tmpfile).bytes().count(); },
        Some(ref name) => { File::open(&**name).unwrap().tee(&mut tmpfile).bytes().count(); },
        None => unreachable!()
    }

    tmpfile.flush().unwrap();

    if let Err(err) = check_crontab_syntax(tmpfile.path()) {
        writeln!(stderr, "syntax error in new crontab file: {}", err).unwrap();
        return 1;
    }

    if let Err(e) = tmpfile.persist(cron_file) {
        writeln!(stderr, "error renaming {} to {}: {}", e.file.path().display(), cron_file.display(), e.error).unwrap();
        return 1;
    }

    change_owner(cron_file, cron_user.uid, cron_user.primary_group).unwrap();

    0
}

fn main() {
    let mut stderr = stderr();
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|e| e.exit());

    let cron_user = match args.flag_user {
        Some(_) if users::get_current_uid() != 0 => {
            writeln!(stderr, "must be privileged to use -u").unwrap();
            exit(1);
        },
        Some(ref user) => match users::get_user_by_name(&**user) {
            Some(user) => user,
            None => {
                writeln!(stderr, "unknown user: {}", user).unwrap();
                exit(1);
            }
        },
        None => users::get_user_by_uid(users::get_current_uid()).unwrap(),
    };

    match fs::metadata(USERS_CRONTAB_DIR) {
        Ok(ref meta) if !meta.is_dir() => {
            writeln!(stderr, "{} is not a directory!", USERS_CRONTAB_DIR).unwrap();
            exit(1);
        },
        Err(_) => if let Err(_) = fs::create_dir_all(USERS_CRONTAB_DIR) {
            writeln!(stderr, "{} doesn't exist!", USERS_CRONTAB_DIR).unwrap();
            exit(1);
        },
        _ => ()
    }

    let cron_file = PathBuf::from(USERS_CRONTAB_DIR).join(cron_user.name.clone());

    exit(match args {
        Args { flag_show: true, .. } => show(&*cron_file, &cron_user, &args),
        Args { flag_list: true, .. } => list(&*cron_file, &cron_user, &args),
        Args { flag_edit: true, arg_file: None, .. } => edit(&*cron_file, &cron_user, &args),
        Args { flag_edit: true, .. } => replace(&*cron_file, &cron_user, &args),
        Args { flag_remove: true, .. } => remove(&*cron_file, &cron_user, &args),
        _ => unreachable!()
    })
}

fn check_crontab_syntax<P: AsRef<Path>>(path: P) -> Result<(), CrontabFileError> {
    match try!(CrontabFile::<UserCrontabEntry>::new(path)).find(Result::is_err) {
        Some(Err(err)) => Err(err),
        _ => Ok(())
    }
}
<commit_msg>avoid warnings<commit_after>#![feature(convert)]
#![feature(io)]

extern crate rustc_serialize;
extern crate docopt;
extern crate users;
extern crate glob;
extern crate tempfile;
extern crate libc;
extern crate cronparse;

use cronparse::{CrontabFile, CrontabFileError};
use cronparse::crontab::UserCrontabEntry;
use tempfile::NamedTempFile;
use docopt::Docopt;
use libc::{uid_t, gid_t};
use users::User;
use std::env;
use std::fs;
use std::io::{stdin, stdout, stderr, Write, Read, self};
use std::fs::File;
use std::os::unix::fs::PermissionsExt;
use std::path::{PathBuf, Path};
use std::process::{Command, exit};
use std::ffi::CString;

include!(concat!(env!("OUT_DIR"), "/config.rs"));

extern "C" {
    fn chown(path: *const libc::c_char, owner: libc::uid_t, group: libc::gid_t) -> libc::c_int;
}

fn change_owner<P: AsRef<Path>>(path: P, owner: libc::uid_t, group: libc::gid_t) -> Result<(), io::Error> {
    match unsafe { chown(CString::new(path.as_ref().as_os_str().to_bytes().unwrap()).unwrap().as_ptr(), owner, group) } {
        0 => Ok(()),
        -1 => Err(io::Error::last_os_error()),
        _ => unreachable!()
    }
}

static USAGE: &'static str = r#"
Usage: crontab [-u <user>] -l
       crontab [-u <user>] -e [<file>]
       crontab [-u <user>] -s
       crontab [-u <user>] -r [-i]
       crontab -h | --help

Maintain crontab files for individual users

Options:

  -h, --help                Show this help message and exit.
  -u <user>, --user <user>  It specifies the name of the user whose crontab is to
                            be tweaked. If this option is not given, crontab
                            examines "your" crontab, i.e., the crontab of the
                            person executing the command. Note that su(8) can
                            confuse crontab and that if you are running inside of
                            su(8) you should always use the -u option for safety's
                            sake. The first form of this command is used to
                            install a new crontab from some named file or standard
                            input if the pseudo-filename "-" is given.
  -l, --list                The current crontab will be displayed on standard
                            output.
  -r, --remove              The current crontab will be removed.
  -e, --edit                This option is used to edit the current crontab using
                            the editor specified by the VISUAL or EDITOR
                            environment variables. After you exit from the editor,
                            the modified crontab will be installed automatically.
  -s, --show                Show all users who have a crontab.
  -i, --ask                 This option modifies the -r option to prompt the user
                            for a 'y/Y' response before actually removing the
                            crontab.
"#;

#[derive(Debug, RustcDecodable)]
#[allow(non_snake_case)]
struct Args {
    arg_file: Option<String>,
    flag_user: Option<String>,
    flag_list: bool,
    flag_remove: bool,
    flag_edit: bool,
    flag_show: bool,
    flag_ask: bool
}

fn get_editor() -> Option<String> {
    env::var("EDITOR").ok()
        .or_else(|| env::var("VISUAL").ok())
        .or_else(|| ["/usr/bin/editor", "/usr/bin/vim", "/usr/bin/nano", "/usr/bin/mcedit"].iter()
                 .find(|editor| fs::metadata(editor).map(|meta| meta.is_file() && meta.permissions().mode() & 0o0111 != 0).unwrap_or(false))
                 .map(|&s| s.to_owned()))
}

fn confirm(msg: &str) -> bool {
    let mut stdin = stdin();
    let mut stdout = stdout();
    loop {
        stdout.write_all(msg.as_bytes()).unwrap();
        stdout.flush().unwrap();
        let mut buf = [0u8; 1024];
        match stdin.read(&mut buf) {
            Ok(n) if n > 0 && (buf[0] == 121 || buf[0] == 89) => return true,
            Ok(n) if n > 0 && (buf[0] == 110 || buf[0] == 78) => return false,
            _ => { stdout.write_all("Please reply \"y\" or \"n\"\n".as_bytes()).unwrap(); },
        }
    }
}

fn list(cron_file: &Path, cron_user: &User, _args: &Args) -> i32 {
    if let Err(e) = File::open(cron_file).map(|file| file.tee(stdout()).bytes().count()) {
        use std::io::ErrorKind::*;
        match e.kind() {
            NotFound => println!("no crontab for {}", cron_user.name),
            _ => println!("failed to read {}: {}", cron_file.display(), e),
        }
        return 1;
    }
    0
}

fn remove(cron_file: &Path, cron_user: &User, args: &Args) -> i32 {
    let mut stderr = stderr();

    if !args.flag_ask || confirm(&*format!("Are you sure you want to delete {} (y/n)? ", cron_file.display())) {
        if let Err(e) = fs::remove_file(cron_file) {
            use std::io::ErrorKind::*;
            match e.kind() {
                NotFound => writeln!(stderr, "no crontab for {}", cron_user.name),
                _ => writeln!(stderr, "failed to remove {}: {}", cron_file.display(), e)
            }.unwrap();
            return 1;
        }
    }
    0
}

fn show(_cron_file: &Path, _cron_user: &User, _args: &Args) -> i32 {
    let mut stderr = stderr();

    if let Ok(dir) = fs::read_dir(USERS_CRONTAB_DIR) {
        for item in dir {
            if let Ok(entry) = item {
                if let Some(name) = entry.file_name().to_str() {
                    if users::get_user_by_name(name).is_some() {
                        println!("{}", name);
                    } else {
                        writeln!(stderr, "WARNING: crontab found with no matching user: {}", name).unwrap();
                    }
                }
            }
        }
    }
    0
}

fn edit(cron_file: &Path, cron_user: &User, _args: &Args) -> i32 {
    use std::io::ErrorKind::*;

    let mut stderr = stderr();

    let editor = match get_editor() {
        None => {
            writeln!(stderr, "no editor found").unwrap();
            return 1;
        },
        Some(editor) => editor
    };

    let mut tmpfile = NamedTempFile::new_in(USERS_CRONTAB_DIR).unwrap();

    if let Err(e) = File::open(cron_file).map(|file| file.tee(&mut tmpfile).bytes().count()) {
        match e.kind() {
            NotFound => tmpfile.write_all("# min hour dom month dow command".as_bytes()).unwrap(),
            _ => {
                writeln!(stderr, "error copying crontab file {}: {}", cron_file.display(), e).unwrap();
                return 1;
            }
        }
    }

    tmpfile.flush().unwrap();
    {
        change_owner(tmpfile.path(), cron_user.uid, cron_user.primary_group).unwrap();
        let _guard = users::switch_user_group(cron_user.uid, cron_user.primary_group);
        match Command::new(editor).arg(tmpfile.path()).status() {
            Ok(status) if status.success() => (),
            _ => {
                writeln!(stderr, "edit aborted, your edit is kept here: {}", tmpfile.path().display()).unwrap();
                return 1;
            }
        }
    }

    if let Err(err) = check_crontab_syntax(tmpfile.path()) {
        writeln!(stderr, "syntax error in new crontab file: {}", err).unwrap();
        return 1;
    }

    if let Err(err) = tmpfile.persist(cron_file) {
        writeln!(stderr, "unexpected error: {}, your edit is kept here: {}", err.error, err.file.path().display()).unwrap();
        return 1;
    }

    0
}

fn replace(cron_file: &Path, cron_user: &User, args: &Args) -> i32 {
    let mut stderr = stderr();
    let mut tmpfile = NamedTempFile::new_in(USERS_CRONTAB_DIR).unwrap();

    match args.arg_file {
        Some(ref name) if &**name == "-" => { stdin().tee(&mut tmpfile).bytes().count(); },
        Some(ref name) => { File::open(&**name).unwrap().tee(&mut tmpfile).bytes().count(); },
        None => unreachable!()
    }

    tmpfile.flush().unwrap();

    if let Err(err) = check_crontab_syntax(tmpfile.path()) {
        writeln!(stderr, "syntax error in new crontab file: {}", err).unwrap();
        return 1;
    }

    if let Err(e) = tmpfile.persist(cron_file) {
        writeln!(stderr, "error renaming {} to {}: {}", e.file.path().display(), cron_file.display(), e.error).unwrap();
        return 1;
    }

    change_owner(cron_file, cron_user.uid, cron_user.primary_group).unwrap();

    0
}

fn main() {
    let mut stderr = stderr();
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|e| e.exit());

    let cron_user = match args.flag_user {
        Some(_) if users::get_current_uid() != 0 => {
            writeln!(stderr, "must be privileged to use -u").unwrap();
            exit(1);
        },
        Some(ref user) => match users::get_user_by_name(&**user) {
            Some(user) => user,
            None => {
                writeln!(stderr, "unknown user: {}", user).unwrap();
                exit(1);
            }
        },
        None => users::get_user_by_uid(users::get_current_uid()).unwrap(),
    };

    match fs::metadata(USERS_CRONTAB_DIR) {
        Ok(ref meta) if !meta.is_dir() => {
            writeln!(stderr, "{} is not a directory!", USERS_CRONTAB_DIR).unwrap();
            exit(1);
        },
        Err(_) => if let Err(_) = fs::create_dir_all(USERS_CRONTAB_DIR) {
            writeln!(stderr, "{} doesn't exist!", USERS_CRONTAB_DIR).unwrap();
            exit(1);
        },
        _ => ()
    }

    let cron_file = PathBuf::from(USERS_CRONTAB_DIR).join(cron_user.name.clone());

    exit(match args {
        Args { flag_show: true, .. } => show(&*cron_file, &cron_user, &args),
        Args { flag_list: true, .. } => list(&*cron_file, &cron_user, &args),
        Args { flag_edit: true, arg_file: None, .. } => edit(&*cron_file, &cron_user, &args),
        Args { flag_edit: true, .. } => replace(&*cron_file, &cron_user, &args),
        Args { flag_remove: true, .. } => remove(&*cron_file, &cron_user, &args),
        _ => unreachable!()
    })
}

fn check_crontab_syntax<P: AsRef<Path>>(path: P) -> Result<(), CrontabFileError> {
    match try!(CrontabFile::<UserCrontabEntry>::new(path)).find(Result::is_err) {
        Some(Err(err)) => Err(err),
        _ => Ok(())
    }
}
<|endoftext|>


<commit_before>#![feature(proc_macro_hygiene, decl_macro)]

extern crate hubcaps;
extern crate pony_playground;
#[macro_use] extern crate rocket;
extern crate rocket_contrib;
extern crate serde;
extern crate serde_derive;
extern crate tokio;
extern crate url;

use rocket::State;
use rocket::response::NamedFile;
use rocket_contrib::json;
use rocket_contrib::json::{Json, JsonValue};
use serde_derive::Deserialize;
use std::io;
use std::path::{PathBuf, Path};
use std::process::Command;
use hubcaps::gists::Gist;
use url::Url;

use pony_playground::*;

#[get("/")]
fn index() -> io::Result<NamedFile> {
    NamedFile::open("static/web.html")
}

#[get("/<file..>")]
fn assets(file: PathBuf) -> Option<NamedFile> {
    NamedFile::open(Path::new("static/").join(file)).ok()
}

#[derive(Deserialize)]
struct Evaluate {
    code: String,
    branch: Option<String>,
}
#[post("/evaluate.json", data = "<request>")]
fn evaluate(request: Json<Evaluate>, playpen: State<Playpen>) -> JsonValue {
    let request = request.0;

    let branch = request.branch.map(|branch| branch.parse().unwrap()).unwrap_or(Branch::Release);

    let (_status, compiler, output) = playpen.evaluate(branch, request.code).unwrap();

    json!({
        "rustc": compiler,
        "program": output,
    })
}

#[derive(Deserialize)]
struct Compile {
    emit: String,
    code: String,
    branch: Option<String>,
}

#[post("/compile.json", data = "<request>")]
fn compile(request: Json<Compile>, playpen: State<Playpen>) -> JsonValue {
    let request = request.0;

    let emit = request.emit.parse().unwrap();
    let branch = request.branch.map(|branch| branch.parse().unwrap()).unwrap_or(Branch::Release);

    let (status, compiler, output) = playpen.compile(branch, request.code, emit).unwrap();

    if status.success() {
        let output = highlight(emit, &output);
        json!({
            "result": output,
        })
    } else {
        json!({
            "error": compiler,
        })
    }
}

#[derive(Deserialize)]
struct CreateGist {
    code: String,
    base_url: String,
    branch: String,
}

fn create_gist(token: String,
               description: String,
               filename: String,
               code: String) -> Gist {
    use tokio::runtime::Runtime;
    use hubcaps::{Credentials, Github};
    use hubcaps::gists::GistOptions;

    let creds = Credentials::Token(token);

    let mut rt = Runtime::new().expect("Unable to create the reactor");
    let github = Github::new("Pony Playground", Some(creds));

    let files = [(filename, code)].iter().cloned().collect();
    let options = GistOptions::builder(files)
        .description(description)
        .public(true)
        .build();

    rt.block_on(github.gists().create(&options)).unwrap()
}

fn update_gist(token: String, id: &str, description: String) -> Gist {
    use tokio::runtime::Runtime;
    use hubcaps::{Credentials, Github};
    use hubcaps::gists::GistOptions;
    use std::collections::HashMap;

    let creds = Credentials::Token(token);

    let mut rt = Runtime::new().expect("Unable to create the reactor");
    let github = Github::new("Pony Playground", Some(creds));

    let options = GistOptions::builder(HashMap::<String, String>::new())
        .description(description)
        .build();

    rt.block_on(github.gists().edit(id, &options)).unwrap()
}

const GIST_FILENAME : &str = "main.pony";
const GIST_DESCRIPTION : &str = "Shared via Pony Playground";

struct GithubToken(String);

#[post("/gist.json", data = "<request>")]
fn gist(request: Json<CreateGist>, token: State<GithubToken>) -> JsonValue {
    let request = request.0;

    let gist = create_gist(
        token.0.clone(), GIST_DESCRIPTION.into(), GIST_FILENAME.into(), request.code);

    let mut url = Url::parse(&request.base_url).unwrap();
    url.query_pairs_mut().append_pair("gist", &gist.id);
    if request.branch != "release" {
        url.query_pairs_mut().append_pair("branch", &request.branch);
    }
    let url = url.into_string();

    update_gist(token.0.clone(), &gist.id, format!("{} ({})", GIST_DESCRIPTION, url));

    json!({
        "gist_id": gist.id,
        "gist_url": gist.html_url,
        "play_url": url,
    })
}

fn main() {
    // Make sure pygmentize is installed before starting the server
    match Command::new("pygmentize").arg("-V").status() {
        Ok(status) if status.success() => (),
        _ => panic!("Cannot find pygmentize."),
    };

    let token = match std::env::var("GITHUB_TOKEN") {
        Ok(token) => token,
        Err(_) => panic!("Missing GITHUB_TOKEN environment variable."),
    };

    rocket::ignite()
        .mount("/", routes![index, assets, evaluate, compile, gist])
        .manage(Playpen::new())
        .manage(GithubToken(token))
        .launch();
}
<commit_msg>Embed the static files in the server binary.<commit_after>#![feature(proc_macro_hygiene, decl_macro)]

extern crate hubcaps;
extern crate pony_playground;
#[macro_use] extern crate rocket;
extern crate rocket_contrib;
extern crate serde;
extern crate serde_derive;
extern crate tokio;
extern crate url;

use hubcaps::gists::Gist;
use rocket::State;
use rocket::http::ContentType;
use rocket::response::content::{Content, Html};
use rocket_contrib::json::{Json, JsonValue};
use rocket_contrib::json;
use serde_derive::Deserialize;
use std::path::PathBuf;
use std::process::Command;
use url::Url;

use pony_playground::{Playpen, Branch, highlight};

#[get("/")]
fn index() -> Html<&'static [u8]> {
    Html(include_bytes!("../../static/web.html"))
}

#[get("/<path..>")]
fn assets(path: PathBuf) -> Option<Content<&'static [u8]>> {
    match path.to_string_lossy().as_ref() {
        "web.css" => {
            Some(Content(ContentType::CSS, include_bytes!("../../static/web.css")))
        }
        "web.js" => {
            Some(Content(ContentType::JavaScript, include_bytes!("../../static/web.js")))
        }
        "mode-pony.js" => {
            Some(Content(ContentType::JavaScript, include_bytes!("../../static/mode-pony.js")))
        }
        _ => None,
    }
}

#[derive(Deserialize)]
struct Evaluate {
    code: String,
    branch: Option<String>,
}
#[post("/evaluate.json", data = "<request>")]
fn evaluate(request: Json<Evaluate>, playpen: State<Playpen>) -> JsonValue {
    let request = request.0;

    let branch = request.branch.map(|branch| branch.parse().unwrap()).unwrap_or(Branch::Release);

    let (_status, compiler, output) = playpen.evaluate(branch, request.code).unwrap();

    json!({
        "rustc": compiler,
        "program": output,
    })
}

#[derive(Deserialize)]
struct Compile {
    emit: String,
    code: String,
    branch: Option<String>,
}

#[post("/compile.json", data = "<request>")]
fn compile(request: Json<Compile>, playpen: State<Playpen>) -> JsonValue {
    let request = request.0;

    let emit = request.emit.parse().unwrap();
    let branch = request.branch.map(|branch| branch.parse().unwrap()).unwrap_or(Branch::Release);

    let (status, compiler, output) = playpen.compile(branch, request.code, emit).unwrap();

    if status.success() {
        let output = highlight(emit, &output);
        json!({
            "result": output,
        })
    } else {
        json!({
            "error": compiler,
        })
    }
}

#[derive(Deserialize)]
struct CreateGist {
    code: String,
    base_url: String,
    branch: String,
}

fn create_gist(token: String,
               description: String,
               filename: String,
               code: String) -> Gist {
    use tokio::runtime::Runtime;
    use hubcaps::{Credentials, Github};
    use hubcaps::gists::GistOptions;

    let creds = Credentials::Token(token);

    let mut rt = Runtime::new().expect("Unable to create the reactor");
    let github = Github::new("Pony Playground", Some(creds));

    let files = [(filename, code)].iter().cloned().collect();
    let options = GistOptions::builder(files)
        .description(description)
        .public(true)
        .build();

    rt.block_on(github.gists().create(&options)).unwrap()
}

fn update_gist(token: String, id: &str, description: String) -> Gist {
    use tokio::runtime::Runtime;
    use hubcaps::{Credentials, Github};
    use hubcaps::gists::GistOptions;
    use std::collections::HashMap;

    let creds = Credentials::Token(token);

    let mut rt = Runtime::new().expect("Unable to create the reactor");
    let github = Github::new("Pony Playground", Some(creds));

    let options = GistOptions::builder(HashMap::<String, String>::new())
        .description(description)
        .build();

    rt.block_on(github.gists().edit(id, &options)).unwrap()
}

const GIST_FILENAME : &str = "main.pony";
const GIST_DESCRIPTION : &str = "Shared via Pony Playground";

struct GithubToken(String);

#[post("/gist.json", data = "<request>")]
fn gist(request: Json<CreateGist>, token: State<GithubToken>) -> JsonValue {
    let request = request.0;

    let gist = create_gist(
        token.0.clone(), GIST_DESCRIPTION.into(), GIST_FILENAME.into(), request.code);

    let mut url = Url::parse(&request.base_url).unwrap();
    url.query_pairs_mut().append_pair("gist", &gist.id);
    if request.branch != "release" {
        url.query_pairs_mut().append_pair("branch", &request.branch);
    }
    let url = url.into_string();

    update_gist(token.0.clone(), &gist.id, format!("{} ({})", GIST_DESCRIPTION, url));

    json!({
        "gist_id": gist.id,
        "gist_url": gist.html_url,
        "play_url": url,
    })
}

fn main() {
    // Make sure pygmentize is installed before starting the server
    match Command::new("pygmentize").arg("-V").status() {
        Ok(status) if status.success() => (),
        _ => panic!("Cannot find pygmentize."),
    };

    let token = match std::env::var("GITHUB_TOKEN") {
        Ok(token) => token,
        Err(_) => panic!("Missing GITHUB_TOKEN environment variable."),
    };

    rocket::ignite()
        .mount("/", routes![index, assets, evaluate, compile, gist])
        .manage(Playpen::new())
        .manage(GithubToken(token))
        .launch();
}
<|endoftext|>


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

#![cfg(not(test))]

#[macro_use]
extern crate log;
extern crate rustfmt;
extern crate toml;
extern crate env_logger;
extern crate getopts;

use rustfmt::{WriteMode, run, run_from_stdin};
use rustfmt::config::Config;

use std::env;
use std::fs::{self, File};
use std::io::{self, Read, Write};
use std::process::Command;
use std::path::{Path, PathBuf};

use getopts::{Matches, Options};

/// Rustfmt operations.
enum Operation {
    /// Format files and their child modules.
    Format(Vec<PathBuf>, WriteMode),
    /// Print the help message.
    Help,
    // Print version information
    Version,
    /// Print detailed configuration help.
    ConfigHelp,
    /// Invalid program input, including reason.
    InvalidInput(String),
    /// No file specified, read from stdin
    Stdin(String, WriteMode),
}

/// Try to find a project file in the input file directory and its parents.
fn lookup_project_file(input_file: &Path) -> io::Result<PathBuf> {
    let mut current = if input_file.is_relative() {
        try!(env::current_dir()).join(input_file)
    } else {
        input_file.to_path_buf()
    };

    // FIXME: We should canonize path to properly handle its parents,
    // but `canonicalize` function is unstable now (recently added API)
    // current = try!(fs::canonicalize(current));

    loop {
        let config_file = current.join("rustfmt.toml");
        if fs::metadata(&config_file).is_ok() {
            return Ok(config_file);
        }

        // If the current directory has no parent, we're done searching.
        if !current.pop() {
            return Err(io::Error::new(io::ErrorKind::NotFound, "Config not found"));
        }
    }
}

/// Try to find a project file. If it's found, read it.
fn lookup_and_read_project_file(input_file: &Path) -> io::Result<(PathBuf, String)> {
    let path = try!(lookup_project_file(input_file));
    let mut file = try!(File::open(&path));
    let mut toml = String::new();
    try!(file.read_to_string(&mut toml));
    Ok((path, toml))
}

fn update_config(config: &mut Config, matches: &Matches) {
    config.verbose = matches.opt_present("verbose");
}

fn execute() -> i32 {
    let mut opts = Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("", "version", "show version information");
    opts.optflag("v", "verbose", "show progress");
    opts.optopt("",
                "write-mode",
                "mode to write in (not usable when piping from stdin)",
                "[replace|overwrite|display|diff|coverage]");

    opts.optflag("",
                 "config-help",
                 "show details of rustfmt configuration options");

    let matches = match opts.parse(env::args().skip(1)) {
        Ok(m) => m,
        Err(e) => {
            print_usage(&opts, &e.to_string());
            return 1;
        }
    };

    let operation = determine_operation(&matches);

    match operation {
        Operation::InvalidInput(reason) => {
            print_usage(&opts, &reason);
            1
        }
        Operation::Help => {
            print_usage(&opts, "");
            0
        }
        Operation::Version => {
            print_version();
            0
        }
        Operation::ConfigHelp => {
            Config::print_docs();
            0
        }
        Operation::Stdin(input, write_mode) => {
            // try to read config from local directory
            let config = match lookup_and_read_project_file(&Path::new(".")) {
                Ok((_, toml)) => {
                    Config::from_toml(&toml)
                }
                Err(_) => Default::default(),
            };

            run_from_stdin(input, write_mode, &config);
            0
        }
        Operation::Format(files, write_mode) => {
            for file in files {
                let mut config = match lookup_and_read_project_file(&file) {
                    Ok((path, toml)) => {
                        println!("Using rustfmt config file {} for {}",
                                 path.display(),
                                 file.display());
                        Config::from_toml(&toml)
                    }
                    Err(_) => Default::default(),
                };

                update_config(&mut config, &matches);
                run(&file, write_mode, &config);
            }
            0
        }
    }
}

fn main() {
    let _ = env_logger::init();
    let exit_code = execute();

    // Make sure standard output is flushed before we exit.
    std::io::stdout().flush().unwrap();

    // Exit with given exit code.
    //
    // NOTE: This immediately terminates the process without doing any cleanup,
    // so make sure to finish all necessary cleanup before this is called.
    std::process::exit(exit_code);
}

fn print_usage(opts: &Options, reason: &str) {
    let reason = format!("{}\nusage: {} [options] <file>...",
                         reason,
                         env::current_exe().unwrap().display());
    println!("{}", opts.usage(&reason));
}

fn print_version() {
    let cmd = Command::new("git")
                  .arg("rev-parse")
                  .arg("--short")
                  .arg("HEAD")
                  .output();
    match cmd {
        Ok(output) => print!("{}", String::from_utf8(output.stdout).unwrap()),
        Err(e) => panic!("Unable te get version: {}", e),
    }
}

fn determine_operation(matches: &Matches) -> Operation {
    if matches.opt_present("h") {
        return Operation::Help;
    }

    if matches.opt_present("config-help") {
        return Operation::ConfigHelp;
    }

    if matches.opt_present("version") {
        return Operation::Version;
    }

    // if no file argument is supplied, read from stdin
    if matches.free.len() == 0 {

        let mut buffer = String::new();
        match io::stdin().read_to_string(&mut buffer) {
            Ok(..) => (),
            Err(e) => return Operation::InvalidInput(e.to_string()),
        }

        // WriteMode is always plain for Stdin
        return Operation::Stdin(buffer, WriteMode::Plain);
    }

    let write_mode = match matches.opt_str("write-mode") {
        Some(mode) => {
            match mode.parse() {
                Ok(mode) => mode,
                Err(..) => return Operation::InvalidInput("Unrecognized write mode".into()),
            }
        }
        None => WriteMode::Replace,
    };

    let files: Vec<_> = matches.free.iter().map(|a| PathBuf::from(a)).collect();

    Operation::Format(files, write_mode)
}
<commit_msg>Add `-V` as a shorthand for `--version`<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![cfg(not(test))]

#[macro_use]
extern crate log;
extern crate rustfmt;
extern crate toml;
extern crate env_logger;
extern crate getopts;

use rustfmt::{WriteMode, run, run_from_stdin};
use rustfmt::config::Config;

use std::env;
use std::fs::{self, File};
use std::io::{self, Read, Write};
use std::process::Command;
use std::path::{Path, PathBuf};

use getopts::{Matches, Options};

/// Rustfmt operations.
enum Operation {
    /// Format files and their child modules.
    Format(Vec<PathBuf>, WriteMode),
    /// Print the help message.
    Help,
    // Print version information
    Version,
    /// Print detailed configuration help.
    ConfigHelp,
    /// Invalid program input, including reason.
    InvalidInput(String),
    /// No file specified, read from stdin
    Stdin(String, WriteMode),
}

/// Try to find a project file in the input file directory and its parents.
fn lookup_project_file(input_file: &Path) -> io::Result<PathBuf> {
    let mut current = if input_file.is_relative() {
        try!(env::current_dir()).join(input_file)
    } else {
        input_file.to_path_buf()
    };

    // FIXME: We should canonize path to properly handle its parents,
    // but `canonicalize` function is unstable now (recently added API)
    // current = try!(fs::canonicalize(current));

    loop {
        let config_file = current.join("rustfmt.toml");
        if fs::metadata(&config_file).is_ok() {
            return Ok(config_file);
        }

        // If the current directory has no parent, we're done searching.
        if !current.pop() {
            return Err(io::Error::new(io::ErrorKind::NotFound, "Config not found"));
        }
    }
}

/// Try to find a project file. If it's found, read it.
fn lookup_and_read_project_file(input_file: &Path) -> io::Result<(PathBuf, String)> {
    let path = try!(lookup_project_file(input_file));
    let mut file = try!(File::open(&path));
    let mut toml = String::new();
    try!(file.read_to_string(&mut toml));
    Ok((path, toml))
}

fn update_config(config: &mut Config, matches: &Matches) {
    config.verbose = matches.opt_present("verbose");
}

fn execute() -> i32 {
    let mut opts = Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("V", "version", "show version information");
    opts.optflag("v", "verbose", "show progress");
    opts.optopt("",
                "write-mode",
                "mode to write in (not usable when piping from stdin)",
                "[replace|overwrite|display|diff|coverage]");

    opts.optflag("",
                 "config-help",
                 "show details of rustfmt configuration options");

    let matches = match opts.parse(env::args().skip(1)) {
        Ok(m) => m,
        Err(e) => {
            print_usage(&opts, &e.to_string());
            return 1;
        }
    };

    let operation = determine_operation(&matches);

    match operation {
        Operation::InvalidInput(reason) => {
            print_usage(&opts, &reason);
            1
        }
        Operation::Help => {
            print_usage(&opts, "");
            0
        }
        Operation::Version => {
            print_version();
            0
        }
        Operation::ConfigHelp => {
            Config::print_docs();
            0
        }
        Operation::Stdin(input, write_mode) => {
            // try to read config from local directory
            let config = match lookup_and_read_project_file(&Path::new(".")) {
                Ok((_, toml)) => {
                    Config::from_toml(&toml)
                }
                Err(_) => Default::default(),
            };

            run_from_stdin(input, write_mode, &config);
            0
        }
        Operation::Format(files, write_mode) => {
            for file in files {
                let mut config = match lookup_and_read_project_file(&file) {
                    Ok((path, toml)) => {
                        println!("Using rustfmt config file {} for {}",
                                 path.display(),
                                 file.display());
                        Config::from_toml(&toml)
                    }
                    Err(_) => Default::default(),
                };

                update_config(&mut config, &matches);
                run(&file, write_mode, &config);
            }
            0
        }
    }
}

fn main() {
    let _ = env_logger::init();
    let exit_code = execute();

    // Make sure standard output is flushed before we exit.
    std::io::stdout().flush().unwrap();

    // Exit with given exit code.
    //
    // NOTE: This immediately terminates the process without doing any cleanup,
    // so make sure to finish all necessary cleanup before this is called.
    std::process::exit(exit_code);
}

fn print_usage(opts: &Options, reason: &str) {
    let reason = format!("{}\nusage: {} [options] <file>...",
                         reason,
                         env::current_exe().unwrap().display());
    println!("{}", opts.usage(&reason));
}

fn print_version() {
    let cmd = Command::new("git")
                  .arg("rev-parse")
                  .arg("--short")
                  .arg("HEAD")
                  .output();
    match cmd {
        Ok(output) => print!("{}", String::from_utf8(output.stdout).unwrap()),
        Err(e) => panic!("Unable te get version: {}", e),
    }
}

fn determine_operation(matches: &Matches) -> Operation {
    if matches.opt_present("h") {
        return Operation::Help;
    }

    if matches.opt_present("config-help") {
        return Operation::ConfigHelp;
    }

    if matches.opt_present("version") {
        return Operation::Version;
    }

    // if no file argument is supplied, read from stdin
    if matches.free.len() == 0 {

        let mut buffer = String::new();
        match io::stdin().read_to_string(&mut buffer) {
            Ok(..) => (),
            Err(e) => return Operation::InvalidInput(e.to_string()),
        }

        // WriteMode is always plain for Stdin
        return Operation::Stdin(buffer, WriteMode::Plain);
    }

    let write_mode = match matches.opt_str("write-mode") {
        Some(mode) => {
            match mode.parse() {
                Ok(mode) => mode,
                Err(..) => return Operation::InvalidInput("Unrecognized write mode".into()),
            }
        }
        None => WriteMode::Replace,
    };

    let files: Vec<_> = matches.free.iter().map(|a| PathBuf::from(a)).collect();

    Operation::Format(files, write_mode)
}
<|endoftext|>


<commit_before>extern crate libc;

use api::ErrorCode;
use commands::agent::RouteCommand;
use commands::{Command, CommandExecutor};
use errors::ToErrorCode;
use utils::ctypes;

use self::libc::c_char;

#[no_mangle]
pub fn indy_auth_pack_message(
    command_handle: i32,
    wallet_handle: i32,
    message: *const c_char,
    recv_keys: *const c_char,
    my_vk: *const c_char,
    cb: Option<extern "C" fn(xcommand_handle: i32, err: ErrorCode, ames: *const c_char)>,
) -> ErrorCode {
    trace!("indy_auth_pack_message: >>> wallet_handle: {:?}, message: {:?}, recv_keys: {:?}, my_vk: {:?}",
           wallet_handle, message, recv_keys, my_vk);

    check_useful_c_str!(message, ErrorCode::CommonInvalidParam3);
    check_useful_c_str!(recv_keys, ErrorCode::CommonInvalidParam4);
    check_useful_c_str!(my_vk, ErrorCode::CommonInvalidParam5);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam6);

    trace!("indy_auth_pack_message: entities >>> wallet_handle: {:?}, message: {:?}, recv_keys: {:?}, my_vk: {:?}",
           wallet_handle, message, recv_keys, my_vk);

    let result = CommandExecutor::instance().send(Command::Route(RouteCommand::AuthPackMessage(
        message,
        recv_keys,
        my_vk,
        wallet_handle,
        Box::new(move |result| {
            let (err, ames) = result_to_err_code_1!(result, String::new());
            trace!(
                "indy_auth_pack_message: cb command_handle: {:?}, err: {:?}, ames: {:?}",
                command_handle,
                err,
                ames
            );
            let ames = ctypes::string_to_cstring(ames);
            cb(command_handle, err, ames.as_ptr())
        }),
    )));

    let res = result_to_err_code!(result);

    trace!("indy_auth_pack_message: <<< res: {:?}", res);

    res
}

#[no_mangle]
pub fn indy_anon_pack_message(
    command_handle: i32,
    message: *const c_char,
    recv_keys: *const c_char,
    cb: Option<extern "C" fn(xcommand_handle: i32, err: ErrorCode, ames: *const c_char)>,
) -> ErrorCode {
    trace!(
        "indy_anon_pack_message: >>> message: {:?}, recv_keys: {:?}",
        message,
        recv_keys
    );

    check_useful_c_str!(message, ErrorCode::CommonInvalidParam3);
    check_useful_c_str!(recv_keys, ErrorCode::CommonInvalidParam4);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam5);

    trace!(
        "indy_anon_pack_message: entities >>> message: {:?}, recv_keys: {:?}",
        message,
        recv_keys
    );

    let result = CommandExecutor::instance().send(Command::Route(RouteCommand::AnonPackMessage(
        message,
        recv_keys,
        Box::new(move |result| {
            let (err, ames) = result_to_err_code_1!(result, String::new());
            trace!(
                "indy_anon_pack_message: cb command_handle: {:?}, err: {:?}, ames: {:?}",
                command_handle,
                err,
                ames
            );
            let verkey = ctypes::string_to_cstring(ames);
            cb(command_handle, err, verkey.as_ptr())
        }),
    )));

    let res = result_to_err_code!(result);

    trace!("indy_anon_pack_message: <<< res: {:?}", res);

    res
}

//update function to return key used
#[no_mangle]
pub fn indy_unpack_message(
    command_handle: i32,
    wallet_handle: i32,
    ames_json: *const c_char,
    my_vk: *const c_char,
    cb: Option<
        extern "C" fn(
            xcommand_handle: i32,
            err: ErrorCode,
            plaintext: *const c_char,
            sender_vk: *const c_char,
        ),
    >,
) -> ErrorCode {
    trace!(
        "indy_unpack_message: >>> wallet_handle: {:?}, ames: {:?}, my_vk: {:?}",
        wallet_handle,
        ames_json,
        my_vk
    );

    check_useful_c_str!(ames_json, ErrorCode::CommonInvalidParam3);
    check_useful_c_str!(my_vk, ErrorCode::CommonInvalidParam4);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam5);

    trace!(
        "indy_unpack_message: entities >>> wallet_handle: {:?}, ames: {:?}, my_vk: {:?}",
        wallet_handle,
        ames_json,
        my_vk
    );

    let result = CommandExecutor::instance().send(Command::Route(RouteCommand::UnpackMessage(
        ames_json,
        my_vk,
        wallet_handle,
        Box::new(move |result| {
            let (err, plaintext, sender_vk) =
                result_to_err_code_2!(result, String::new(), String::new());
            trace!(
                "indy_unpack_message: cb command_handle: {:?}, err: {:?}, plaintext: {:?}",
                command_handle,
                err,
                plaintext
            );
            let plaintext = ctypes::string_to_cstring(plaintext);
            let sender_vk = ctypes::string_to_cstring(sender_vk);
            cb(command_handle, err, plaintext.as_ptr(), sender_vk.as_ptr())
        }),
    )));

    let res = result_to_err_code!(result);

    trace!("indy_unpack_message: <<< res: {:?}", res);

    res
}
<commit_msg>refactored API layer<commit_after>extern crate libc;

use api::ErrorCode;
use commands::agent::RouteCommand;
use commands::{Command, CommandExecutor};
use errors::ToErrorCode;
use utils::ctypes;

use self::libc::c_char;

#[no_mangle]
pub fn indy_auth_pack_message(
    command_handle: i32,
    wallet_handle: i32,
    message: *const c_char,
    receiver_keys: *const c_char,
    sender_verkey: *const c_char,
    cb: Option<extern "C" fn(xcommand_handle: i32, err: ErrorCode, jwe: *const c_char)>,
) -> ErrorCode {
    trace!("indy_auth_pack_message: >>> wallet_handle: {:?}, message: {:?}, recv_keys: {:?}, my_vk: {:?}",
           wallet_handle, message, receiver_keys, sender_verkey);

    check_useful_c_str!(message, ErrorCode::CommonInvalidParam3);
    check_useful_c_str!(recv_keys, ErrorCode::CommonInvalidParam4);
    check_useful_c_str!(my_vk, ErrorCode::CommonInvalidParam5);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam6);

    trace!("indy_auth_pack_message: entities >>> wallet_handle: {:?}, message: {:?}, recv_keys: {:?}, my_vk: {:?}",
           wallet_handle, message, receiver_keys, sender_verkey);

    let result = CommandExecutor::instance().send(Command::Route(RouteCommand::AuthPackMessage(
        message,
        recveiver_keys,
        sender_verkey,
        wallet_handle,
        Box::new(move |result| {
            let (err, jwe) = result_to_err_code_1!(result, String::new());
            trace!(
                "indy_auth_pack_message: cb command_handle: {:?}, err: {:?}, jwe: {:?}",
                command_handle,
                err,
                jwe
            );
            let jwe = ctypes::string_to_cstring(jwe);
            cb(command_handle, err, jwe.as_ptr())
        }),
    )));

    let res = result_to_err_code!(result);

    trace!("indy_auth_pack_message: <<< res: {:?}", res);

    res
}

#[no_mangle]
pub fn indy_anon_pack_message(
    command_handle: i32,
    message: *const c_char,
    recveiver_keys: *const c_char,
    cb: Option<extern "C" fn(xcommand_handle: i32, err: ErrorCode, jwe: *const c_char)>,
) -> ErrorCode {
    trace!(
        "indy_anon_pack_message: >>> message: {:?}, recv_keys: {:?}",
        message,
        recveiver_keys
    );

    check_useful_c_str!(message, ErrorCode::CommonInvalidParam3);
    check_useful_c_str!(recv_keys, ErrorCode::CommonInvalidParam4);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam5);

    trace!(
        "indy_anon_pack_message: entities >>> message: {:?}, recv_keys: {:?}",
        message,
        recveiver_keys
    );

    let result = CommandExecutor::instance().send(Command::Route(RouteCommand::AnonPackMessage(
        message,
        recveiver_keys,
        Box::new(move |result| {
            let (err, jwe) = result_to_err_code_1!(result, String::new());
            trace!(
                "indy_anon_pack_message: cb command_handle: {:?}, err: {:?}, jwe: {:?}",
                command_handle,
                err,
                jwe
            );
            let verkey = ctypes::string_to_cstring(jwe);
            cb(command_handle, err, verkey.as_ptr())
        }),
    )));

    let res = result_to_err_code!(result);

    trace!("indy_anon_pack_message: <<< res: {:?}", res);

    res
}

//update function to return key used
#[no_mangle]
pub fn indy_unpack_message(
    command_handle: i32,
    wallet_handle: i32,
    jwe: *const c_char,
    sender_verkey: *const c_char,
    cb: Option<
        extern "C" fn(
            xcommand_handle: i32,
            err: ErrorCode,
            plaintext: *const c_char,
            sender_vk: *const c_char,
        ),
    >,
) -> ErrorCode {
    trace!(
        "indy_unpack_message: >>> wallet_handle: {:?}, jwe: {:?}, my_vk: {:?}",
        wallet_handle,
        jwe,
        sender_verkey
    );

    check_useful_c_str!(jwe_json, ErrorCode::CommonInvalidParam3);
    check_useful_c_str!(my_vk, ErrorCode::CommonInvalidParam4);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam5);

    trace!(
        "indy_unpack_message: entities >>> wallet_handle: {:?}, jwe: {:?}, my_vk: {:?}",
        wallet_handle,
        jwe,
        sender_verkey
    );

    let result = CommandExecutor::instance().send(Command::Route(RouteCommand::UnpackMessage(
        jwe,
        sender_verkey,
        wallet_handle,
        Box::new(move |result| {
            let (err, plaintext, sender_vk) =
                result_to_err_code_2!(result, String::new(), String::new());
            trace!(
                "indy_unpack_message: cb command_handle: {:?}, err: {:?}, plaintext: {:?}",
                command_handle,
                err,
                plaintext
            );
            let plaintext = ctypes::string_to_cstring(plaintext);
            let sender_vk = ctypes::string_to_cstring(sender_vk);
            cb(command_handle, err, plaintext.as_ptr(), sender_vk.as_ptr())
        }),
    )));

    let res = result_to_err_code!(result);

    trace!("indy_unpack_message: <<< res: {:?}", res);

    res
}
<|endoftext|>


<commit_before>use std::{
    fs::File,
    io::{self, BufRead, Read as _},
    ops::Range,
};

use crate::common::{
    net::{self, NetError, ServerCmd},
    util::read_f32_3,
    vfs::VirtualFile,
};

use arrayvec::ArrayVec;
use byteorder::{LittleEndian, ReadBytesExt};
use cgmath::{Deg, Vector3};
use io::BufReader;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DemoServerError {
    #[error("Invalid CD track number")]
    InvalidCdTrack,
    #[error("No such CD track: {0}")]
    NoSuchCdTrack(i32),
    #[error("Message size ({0}) exceeds maximum allowed size {}", net::MAX_MESSAGE)]
    MessageTooLong(u32),
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),
    #[error("Network error: {0}")]
    Net(#[from] NetError),
}

struct DemoMessage {
    view_angles: Vector3<Deg<f32>>,
    msg_range: Range<usize>,
}

pub struct DemoMessageView<'a> {
    view_angles: Vector3<Deg<f32>>,
    message: &'a [u8],
}

impl<'a> DemoMessageView<'a> {
    pub fn view_angles(&self) -> Vector3<Deg<f32>> {
        self.view_angles
    }

    pub fn message(&self) -> &[u8] {
        self.message
    }
}

pub struct DemoServer {
    track_override: Option<u32>,

    // id of next message to "send"
    message_id: usize,

    messages: Vec<DemoMessage>,

    // all message data
    message_data: Vec<u8>,
}

impl DemoServer {
    pub fn new(file: &mut VirtualFile) -> Result<DemoServer, DemoServerError> {
        let mut dem_reader = BufReader::new(file);
        let mut buf = ArrayVec::<[u8; net::MAX_MESSAGE]>::new();

        // copy CD track number (terminated by newline) into buffer
        for i in 0..3 {
            match dem_reader.read_u8()? {
                b'\n' => break,
                // cannot panic because we won't exceed capacity with a loop this small
                b => buf.push(b),
            }

            if i > 1 {
                // CD track would be more than 2 digits long, which is impossible
                Err(DemoServerError::InvalidCdTrack)?;
            }
        }

        let track_override = {
            let track_str = match std::str::from_utf8(&buf[..buf.len() - 1]) {
                Ok(s) => s,
                Err(_) => Err(DemoServerError::InvalidCdTrack)?,
            };

            match track_str {
                // if track is empty, default to track 0
                "" => Some(0),
                s => match s.parse::<i32>() {
                    Ok(track) => match track {
                        // if track is -1, allow demo to specify tracks in messages
                        -1 => None,
                        t if t < -1 => Err(DemoServerError::InvalidCdTrack)?,
                        _ => Some(track as u32),
                    },
                    Err(_) => Err(DemoServerError::InvalidCdTrack)?,
                },
            }
        };
        // TODO: verify that track exists

        let mut message_data = Vec::new();
        let mut messages = Vec::new();

        // read all messages
        while let Ok(msg_len) = dem_reader.read_u32::<LittleEndian>() {
            // get view angles
            let view_angles_f32 = read_f32_3(&mut dem_reader)?;
            let view_angles = Vector3::new(
                Deg(view_angles_f32[0]),
                Deg(view_angles_f32[1]),
                Deg(view_angles_f32[2]),
            );

            // read next message
            let msg_start = message_data.len();
            for _ in 0..msg_len {
                message_data.push(dem_reader.read_u8()?);
            }
            let msg_end = message_data.len();

            messages.push(DemoMessage {
                view_angles,
                msg_range: msg_start..msg_end,
            });
        }

        Ok(DemoServer {
            track_override,
            message_id: 0,
            messages,
            message_data,
        })
    }

    pub fn next(&mut self) -> Option<DemoMessageView> {
        if self.message_id >= self.messages.len() {
            return None;
        }

        let msg = &self.messages[self.message_id];
        self.message_id += 1;

        Some(DemoMessageView {
            view_angles: msg.view_angles,
            message: &self.message_data[msg.msg_range.clone()],
        })
    }
}
<commit_msg>Use smaller arrayvec for CD track in demos<commit_after>use std::{
    fs::File,
    io::{self, BufRead, Read as _},
    ops::Range,
};

use crate::common::{
    net::{self, NetError, ServerCmd},
    util::read_f32_3,
    vfs::VirtualFile,
};

use arrayvec::ArrayVec;
use byteorder::{LittleEndian, ReadBytesExt};
use cgmath::{Deg, Vector3};
use io::BufReader;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DemoServerError {
    #[error("Invalid CD track number")]
    InvalidCdTrack,
    #[error("No such CD track: {0}")]
    NoSuchCdTrack(i32),
    #[error("Message size ({0}) exceeds maximum allowed size {}", net::MAX_MESSAGE)]
    MessageTooLong(u32),
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),
    #[error("Network error: {0}")]
    Net(#[from] NetError),
}

struct DemoMessage {
    view_angles: Vector3<Deg<f32>>,
    msg_range: Range<usize>,
}

pub struct DemoMessageView<'a> {
    view_angles: Vector3<Deg<f32>>,
    message: &'a [u8],
}

impl<'a> DemoMessageView<'a> {
    pub fn view_angles(&self) -> Vector3<Deg<f32>> {
        self.view_angles
    }

    pub fn message(&self) -> &[u8] {
        self.message
    }
}

pub struct DemoServer {
    track_override: Option<u32>,

    // id of next message to "send"
    message_id: usize,

    messages: Vec<DemoMessage>,

    // all message data
    message_data: Vec<u8>,
}

impl DemoServer {
    pub fn new(file: &mut VirtualFile) -> Result<DemoServer, DemoServerError> {
        let mut dem_reader = BufReader::new(file);

        let mut buf = ArrayVec::<[u8; 3]>::new();
        // copy CD track number (terminated by newline) into buffer
        for i in 0..buf.capacity() {
            match dem_reader.read_u8()? {
                b'\n' => break,
                // cannot panic because we won't exceed capacity with a loop this small
                b => buf.push(b),
            }

            if i >= buf.capacity() - 1 {
                // CD track would be more than 2 digits long, which is impossible
                Err(DemoServerError::InvalidCdTrack)?;
            }
        }

        let track_override = {
            let track_str = match std::str::from_utf8(&buf) {
                Ok(s) => s,
                Err(_) => Err(DemoServerError::InvalidCdTrack)?,
            };

            match track_str {
                // if track is empty, default to track 0
                "" => Some(0),
                s => match s.parse::<i32>() {
                    Ok(track) => match track {
                        // if track is -1, allow demo to specify tracks in messages
                        -1 => None,
                        t if t < -1 => Err(DemoServerError::InvalidCdTrack)?,
                        _ => Some(track as u32),
                    },
                    Err(_) => Err(DemoServerError::InvalidCdTrack)?,
                },
            }
        };
        // TODO: verify that track exists

        let mut message_data = Vec::new();
        let mut messages = Vec::new();

        // read all messages
        while let Ok(msg_len) = dem_reader.read_u32::<LittleEndian>() {
            // get view angles
            let view_angles_f32 = read_f32_3(&mut dem_reader)?;
            let view_angles = Vector3::new(
                Deg(view_angles_f32[0]),
                Deg(view_angles_f32[1]),
                Deg(view_angles_f32[2]),
            );

            // read next message
            let msg_start = message_data.len();
            for _ in 0..msg_len {
                message_data.push(dem_reader.read_u8()?);
            }
            let msg_end = message_data.len();

            messages.push(DemoMessage {
                view_angles,
                msg_range: msg_start..msg_end,
            });
        }

        Ok(DemoServer {
            track_override,
            message_id: 0,
            messages,
            message_data,
        })
    }

    pub fn next(&mut self) -> Option<DemoMessageView> {
        if self.message_id >= self.messages.len() {
            return None;
        }

        let msg = &self.messages[self.message_id];
        self.message_id += 1;

        Some(DemoMessageView {
            view_angles: msg.view_angles,
            message: &self.message_data[msg.msg_range.clone()],
        })
    }
}
<|endoftext|>