<commit_before>//! Script to check the validity of `href` links in our HTML documentation.
//!
//! In the past we've been quite error prone to writing in broken links as most
//! of them are manually rather than automatically added. As files move over
//! time or apis change old links become stale or broken. The purpose of this
//! script is to check all relative links in our documentation to make sure they
//! actually point to a valid place.
//!
//! Currently this doesn't actually do any HTML parsing or anything fancy like
//! that, it just has a simple "regex" to search for `href` and `id` tags.
//! These values are then translated to file URLs if possible and then the
//! destination is asserted to exist.
//!
//! A few exceptions are allowed as there's known bugs in rustdoc, but this
//! should catch the majority of "broken link" cases.

use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::path::{Component, Path, PathBuf};
use std::rc::Rc;

use crate::Redirect::*;

// Add linkcheck exceptions here
// If at all possible you should use intra-doc links to avoid linkcheck issues. These
// are cases where that does not work
const LINKCHECK_EXCEPTIONS: &[(&str, &[&str])] = &[
    // These are methods on slice, and `Self` does not work on primitive impls
    // in intra-doc links (intra-doc links are weird)
    // https://github.com/rust-lang/rust/issues/62834 is necessary to be
    // able to link to slices
    (
        "std/io/struct.IoSlice.html",
        &[
            "#method.as_mut_ptr",
            "#method.sort_by_key",
            "#method.make_ascii_uppercase",
            "#method.make_ascii_lowercase",
        ],
    ),
    // These try to link to std::collections, but are defined in alloc
    // https://github.com/rust-lang/rust/issues/74481
    ("std/collections/btree_map/struct.BTreeMap.html", &["#insert-and-complex-keys"]),
    ("std/collections/btree_set/struct.BTreeSet.html", &["#insert-and-complex-keys"]),
    ("alloc/collections/btree_map/struct.BTreeMap.html", &["#insert-and-complex-keys"]),
    ("alloc/collections/btree_set/struct.BTreeSet.html", &["#insert-and-complex-keys"]),
];

macro_rules! t {
    ($e:expr) => {
        match $e {
            Ok(e) => e,
            Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
        }
    };
}

fn main() {
    let docs = env::args_os().nth(1).unwrap();
    let docs = env::current_dir().unwrap().join(docs);
    let mut errors = false;
    walk(&mut HashMap::new(), &docs, &docs, &mut errors);
    if errors {
        panic!("found some broken links");
    }
}

#[derive(Debug)]
pub enum LoadError {
    IOError(std::io::Error),
    BrokenRedirect(PathBuf, std::io::Error),
    IsRedirect,
}

enum Redirect {
    SkipRedirect,
    FromRedirect(bool),
}

struct FileEntry {
    source: Rc<String>,
    ids: HashSet<String>,
}

type Cache = HashMap<PathBuf, FileEntry>;

fn small_url_encode(s: &str) -> String {
    s.replace("<", "%3C")
        .replace(">", "%3E")
        .replace(" ", "%20")
        .replace("?", "%3F")
        .replace("'", "%27")
        .replace("&", "%26")
        .replace(",", "%2C")
        .replace(":", "%3A")
        .replace(";", "%3B")
        .replace("[", "%5B")
        .replace("]", "%5D")
        .replace("\"", "%22")
}

impl FileEntry {
    fn parse_ids(&mut self, file: &Path, contents: &str, errors: &mut bool) {
        if self.ids.is_empty() {
            with_attrs_in_source(contents, " id", |fragment, i, _| {
                let frag = fragment.trim_start_matches("#").to_owned();
                let encoded = small_url_encode(&frag);
                if !self.ids.insert(frag) {
                    *errors = true;
                    println!("{}:{}: id is not unique: `{}`", file.display(), i, fragment);
                }
                // Just in case, we also add the encoded id.
                self.ids.insert(encoded);
            });
        }
    }
}

fn walk(cache: &mut Cache, root: &Path, dir: &Path, errors: &mut bool) {
    for entry in t!(dir.read_dir()).map(|e| t!(e)) {
        let path = entry.path();
        let kind = t!(entry.file_type());
        if kind.is_dir() {
            walk(cache, root, &path, errors);
        } else {
            let pretty_path = check(cache, root, &path, errors);
            if let Some(pretty_path) = pretty_path {
                let entry = cache.get_mut(&pretty_path).unwrap();
                // we don't need the source anymore,
                // so drop to reduce memory-usage
                entry.source = Rc::new(String::new());
            }
        }
    }
}

fn is_exception(file: &Path, link: &str) -> bool {
    if let Some(entry) = LINKCHECK_EXCEPTIONS.iter().find(|&(f, _)| file.ends_with(f)) {
        entry.1.contains(&link)
    } else {
        false
    }
}

fn check(cache: &mut Cache, root: &Path, file: &Path, errors: &mut bool) -> Option<PathBuf> {
    // Ignore non-HTML files.
    if file.extension().and_then(|s| s.to_str()) != Some("html") {
        return None;
    }

    let res = load_file(cache, root, file, SkipRedirect);
    let (pretty_file, contents) = match res {
        Ok(res) => res,
        Err(_) => return None,
    };
    {
        cache.get_mut(&pretty_file).unwrap().parse_ids(&pretty_file, &contents, errors);
    }

    // Search for anything that's the regex 'href[ ]*=[ ]*".*?"'
    with_attrs_in_source(&contents, " href", |url, i, base| {
        // Ignore external URLs
        if url.starts_with("http:")
            || url.starts_with("https:")
            || url.starts_with("javascript:")
            || url.starts_with("ftp:")
            || url.starts_with("irc:")
            || url.starts_with("data:")
        {
            return;
        }
        let mut parts = url.splitn(2, "#");
        let url = parts.next().unwrap();
        let fragment = parts.next();
        let mut parts = url.splitn(2, "?");
        let url = parts.next().unwrap();

        // Once we've plucked out the URL, parse it using our base url and
        // then try to extract a file path.
        let mut path = file.to_path_buf();
        if !base.is_empty() || !url.is_empty() {
            path.pop();
            for part in Path::new(base).join(url).components() {
                match part {
                    Component::Prefix(_) | Component::RootDir => {
                        // Avoid absolute paths as they make the docs not
                        // relocatable by making assumptions on where the docs
                        // are hosted relative to the site root.
                        *errors = true;
                        println!(
                            "{}:{}: absolute path - {}",
                            pretty_file.display(),
                            i + 1,
                            Path::new(base).join(url).display()
                        );
                        return;
                    }
                    Component::CurDir => {}
                    Component::ParentDir => {
                        path.pop();
                    }
                    Component::Normal(s) => {
                        path.push(s);
                    }
                }
            }
        }

        // Alright, if we've found a file name then this file had better
        // exist! If it doesn't then we register and print an error.
        if path.exists() {
            if path.is_dir() {
                // Links to directories show as directory listings when viewing
                // the docs offline so it's best to avoid them.
                *errors = true;
                let pretty_path = path.strip_prefix(root).unwrap_or(&path);
                println!(
                    "{}:{}: directory link - {}",
                    pretty_file.display(),
                    i + 1,
                    pretty_path.display()
                );
                return;
            }
            if let Some(extension) = path.extension() {
                // Ignore none HTML files.
                if extension != "html" {
                    return;
                }
            }
            let res = load_file(cache, root, &path, FromRedirect(false));
            let (pretty_path, contents) = match res {
                Ok(res) => res,
                Err(LoadError::IOError(err)) => {
                    panic!("error loading {}: {}", path.display(), err);
                }
                Err(LoadError::BrokenRedirect(target, _)) => {
                    *errors = true;
                    println!(
                        "{}:{}: broken redirect to {}",
                        pretty_file.display(),
                        i + 1,
                        target.display()
                    );
                    return;
                }
                Err(LoadError::IsRedirect) => unreachable!(),
            };

            if let Some(ref fragment) = fragment {
                // Fragments like `#1-6` are most likely line numbers to be
                // interpreted by javascript, so we're ignoring these
                if fragment.splitn(2, '-').all(|f| f.chars().all(|c| c.is_numeric())) {
                    return;
                }

                // These appear to be broken in mdbook right now?
                if fragment.starts_with("-") {
                    return;
                }

                let entry = &mut cache.get_mut(&pretty_path).unwrap();
                entry.parse_ids(&pretty_path, &contents, errors);

                if !entry.ids.contains(*fragment) && !is_exception(file, &format!("#{}", fragment))
                {
                    *errors = true;
                    print!("{}:{}: broken link fragment ", pretty_file.display(), i + 1);
                    println!("`#{}` pointing to `{}`", fragment, pretty_path.display());
                };
            }
        } else {
            let pretty_path = path.strip_prefix(root).unwrap_or(&path);
            if !is_exception(file, pretty_path.to_str().unwrap()) {
                *errors = true;
                print!("{}:{}: broken link - ", pretty_file.display(), i + 1);
                println!("{}", pretty_path.display());
            }
        }
    });
    Some(pretty_file)
}

fn load_file(
    cache: &mut Cache,
    root: &Path,
    file: &Path,
    redirect: Redirect,
) -> Result<(PathBuf, Rc<String>), LoadError> {
    let pretty_file = PathBuf::from(file.strip_prefix(root).unwrap_or(&file));

    let (maybe_redirect, contents) = match cache.entry(pretty_file.clone()) {
        Entry::Occupied(entry) => (None, entry.get().source.clone()),
        Entry::Vacant(entry) => {
            let contents = match fs::read_to_string(file) {
                Ok(s) => Rc::new(s),
                Err(err) => {
                    return Err(if let FromRedirect(true) = redirect {
                        LoadError::BrokenRedirect(file.to_path_buf(), err)
                    } else {
                        LoadError::IOError(err)
                    });
                }
            };

            let maybe = maybe_redirect(&contents);
            if maybe.is_some() {
                if let SkipRedirect = redirect {
                    return Err(LoadError::IsRedirect);
                }
            } else {
                entry.insert(FileEntry { source: contents.clone(), ids: HashSet::new() });
            }
            (maybe, contents)
        }
    };
    match maybe_redirect.map(|url| file.parent().unwrap().join(url)) {
        Some(redirect_file) => load_file(cache, root, &redirect_file, FromRedirect(true)),
        None => Ok((pretty_file, contents)),
    }
}

fn maybe_redirect(source: &str) -> Option<String> {
    const REDIRECT: &'static str = "
Redirecting to <a href=";

    let mut lines = source.lines();
    let redirect_line = lines.nth(6)?;

    redirect_line.find(REDIRECT).map(|i| {
        let rest = &redirect_line[(i + REDIRECT.len() + 1)..];
        let pos_quote = rest.find('"').unwrap();
        rest[..pos_quote].to_owned()
    })
}

fn with_attrs_in_source<F: FnMut(&str, usize, &str)>(contents: &str, attr: &str, mut f: F) {
    let mut base = "";
    for (i, mut line) in contents.lines().enumerate() {
        while let Some(j) = line.find(attr) {
            let rest = &line[j + attr.len()..];
            // The base tag should always be the first link in the document so
            // we can get away with using one pass.
            let is_base = line[..j].ends_with("<base");
            line = rest;
            let pos_equals = match rest.find("=") {
                Some(i) => i,
                None => continue,
            };
            if rest[..pos_equals].trim_start_matches(" ") != "" {
                continue;
            }

            let rest = &rest[pos_equals + 1..];

            let pos_quote = match rest.find(&['"', '\''][..]) {
                Some(i) => i,
                None => continue,
            };
            let quote_delim = rest.as_bytes()[pos_quote] as char;

            if rest[..pos_quote].trim_start_matches(" ") != "" {
                continue;
            }
            let rest = &rest[pos_quote + 1..];
            let url = match rest.find(quote_delim) {
                Some(i) => &rest[..i],
                None => continue,
            };
            if is_base {
                base = url;
                continue;
            }
            f(url, i, base)
        }
    }
}
<commit_msg>Update src/tools/linkchecker/main.rs<commit_after>//! Script to check the validity of `href` links in our HTML documentation.
//!
//! In the past we've been quite error prone to writing in broken links as most
//! of them are manually rather than automatically added. As files move over
//! time or apis change old links become stale or broken. The purpose of this
//! script is to check all relative links in our documentation to make sure they
//! actually point to a valid place.
//!
//! Currently this doesn't actually do any HTML parsing or anything fancy like
//! that, it just has a simple "regex" to search for `href` and `id` tags.
//! These values are then translated to file URLs if possible and then the
//! destination is asserted to exist.
//!
//! A few exceptions are allowed as there's known bugs in rustdoc, but this
//! should catch the majority of "broken link" cases.

use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::path::{Component, Path, PathBuf};
use std::rc::Rc;

use crate::Redirect::*;

// Add linkcheck exceptions here
// If at all possible you should use intra-doc links to avoid linkcheck issues. These
// are cases where that does not work
// [(generated_documentation_page, &[broken_links])]
const LINKCHECK_EXCEPTIONS: &[(&str, &[&str])] = &[
    // These are methods on slice, and `Self` does not work on primitive impls
    // in intra-doc links (intra-doc links are weird)
    // https://github.com/rust-lang/rust/issues/62834 is necessary to be
    // able to link to slices
    (
        "std/io/struct.IoSlice.html",
        &[
            "#method.as_mut_ptr",
            "#method.sort_by_key",
            "#method.make_ascii_uppercase",
            "#method.make_ascii_lowercase",
        ],
    ),
    // These try to link to std::collections, but are defined in alloc
    // https://github.com/rust-lang/rust/issues/74481
    ("std/collections/btree_map/struct.BTreeMap.html", &["#insert-and-complex-keys"]),
    ("std/collections/btree_set/struct.BTreeSet.html", &["#insert-and-complex-keys"]),
    ("alloc/collections/btree_map/struct.BTreeMap.html", &["#insert-and-complex-keys"]),
    ("alloc/collections/btree_set/struct.BTreeSet.html", &["#insert-and-complex-keys"]),
];

macro_rules! t {
    ($e:expr) => {
        match $e {
            Ok(e) => e,
            Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
        }
    };
}

fn main() {
    let docs = env::args_os().nth(1).unwrap();
    let docs = env::current_dir().unwrap().join(docs);
    let mut errors = false;
    walk(&mut HashMap::new(), &docs, &docs, &mut errors);
    if errors {
        panic!("found some broken links");
    }
}

#[derive(Debug)]
pub enum LoadError {
    IOError(std::io::Error),
    BrokenRedirect(PathBuf, std::io::Error),
    IsRedirect,
}

enum Redirect {
    SkipRedirect,
    FromRedirect(bool),
}

struct FileEntry {
    source: Rc<String>,
    ids: HashSet<String>,
}

type Cache = HashMap<PathBuf, FileEntry>;

fn small_url_encode(s: &str) -> String {
    s.replace("<", "%3C")
        .replace(">", "%3E")
        .replace(" ", "%20")
        .replace("?", "%3F")
        .replace("'", "%27")
        .replace("&", "%26")
        .replace(",", "%2C")
        .replace(":", "%3A")
        .replace(";", "%3B")
        .replace("[", "%5B")
        .replace("]", "%5D")
        .replace("\"", "%22")
}

impl FileEntry {
    fn parse_ids(&mut self, file: &Path, contents: &str, errors: &mut bool) {
        if self.ids.is_empty() {
            with_attrs_in_source(contents, " id", |fragment, i, _| {
                let frag = fragment.trim_start_matches("#").to_owned();
                let encoded = small_url_encode(&frag);
                if !self.ids.insert(frag) {
                    *errors = true;
                    println!("{}:{}: id is not unique: `{}`", file.display(), i, fragment);
                }
                // Just in case, we also add the encoded id.
                self.ids.insert(encoded);
            });
        }
    }
}

fn walk(cache: &mut Cache, root: &Path, dir: &Path, errors: &mut bool) {
    for entry in t!(dir.read_dir()).map(|e| t!(e)) {
        let path = entry.path();
        let kind = t!(entry.file_type());
        if kind.is_dir() {
            walk(cache, root, &path, errors);
        } else {
            let pretty_path = check(cache, root, &path, errors);
            if let Some(pretty_path) = pretty_path {
                let entry = cache.get_mut(&pretty_path).unwrap();
                // we don't need the source anymore,
                // so drop to reduce memory-usage
                entry.source = Rc::new(String::new());
            }
        }
    }
}

fn is_exception(file: &Path, link: &str) -> bool {
    if let Some(entry) = LINKCHECK_EXCEPTIONS.iter().find(|&(f, _)| file.ends_with(f)) {
        entry.1.contains(&link)
    } else {
        false
    }
}

fn check(cache: &mut Cache, root: &Path, file: &Path, errors: &mut bool) -> Option<PathBuf> {
    // Ignore non-HTML files.
    if file.extension().and_then(|s| s.to_str()) != Some("html") {
        return None;
    }

    let res = load_file(cache, root, file, SkipRedirect);
    let (pretty_file, contents) = match res {
        Ok(res) => res,
        Err(_) => return None,
    };
    {
        cache.get_mut(&pretty_file).unwrap().parse_ids(&pretty_file, &contents, errors);
    }

    // Search for anything that's the regex 'href[ ]*=[ ]*".*?"'
    with_attrs_in_source(&contents, " href", |url, i, base| {
        // Ignore external URLs
        if url.starts_with("http:")
            || url.starts_with("https:")
            || url.starts_with("javascript:")
            || url.starts_with("ftp:")
            || url.starts_with("irc:")
            || url.starts_with("data:")
        {
            return;
        }
        let mut parts = url.splitn(2, "#");
        let url = parts.next().unwrap();
        let fragment = parts.next();
        let mut parts = url.splitn(2, "?");
        let url = parts.next().unwrap();

        // Once we've plucked out the URL, parse it using our base url and
        // then try to extract a file path.
        let mut path = file.to_path_buf();
        if !base.is_empty() || !url.is_empty() {
            path.pop();
            for part in Path::new(base).join(url).components() {
                match part {
                    Component::Prefix(_) | Component::RootDir => {
                        // Avoid absolute paths as they make the docs not
                        // relocatable by making assumptions on where the docs
                        // are hosted relative to the site root.
                        *errors = true;
                        println!(
                            "{}:{}: absolute path - {}",
                            pretty_file.display(),
                            i + 1,
                            Path::new(base).join(url).display()
                        );
                        return;
                    }
                    Component::CurDir => {}
                    Component::ParentDir => {
                        path.pop();
                    }
                    Component::Normal(s) => {
                        path.push(s);
                    }
                }
            }
        }

        // Alright, if we've found a file name then this file had better
        // exist! If it doesn't then we register and print an error.
        if path.exists() {
            if path.is_dir() {
                // Links to directories show as directory listings when viewing
                // the docs offline so it's best to avoid them.
                *errors = true;
                let pretty_path = path.strip_prefix(root).unwrap_or(&path);
                println!(
                    "{}:{}: directory link - {}",
                    pretty_file.display(),
                    i + 1,
                    pretty_path.display()
                );
                return;
            }
            if let Some(extension) = path.extension() {
                // Ignore none HTML files.
                if extension != "html" {
                    return;
                }
            }
            let res = load_file(cache, root, &path, FromRedirect(false));
            let (pretty_path, contents) = match res {
                Ok(res) => res,
                Err(LoadError::IOError(err)) => {
                    panic!("error loading {}: {}", path.display(), err);
                }
                Err(LoadError::BrokenRedirect(target, _)) => {
                    *errors = true;
                    println!(
                        "{}:{}: broken redirect to {}",
                        pretty_file.display(),
                        i + 1,
                        target.display()
                    );
                    return;
                }
                Err(LoadError::IsRedirect) => unreachable!(),
            };

            if let Some(ref fragment) = fragment {
                // Fragments like `#1-6` are most likely line numbers to be
                // interpreted by javascript, so we're ignoring these
                if fragment.splitn(2, '-').all(|f| f.chars().all(|c| c.is_numeric())) {
                    return;
                }

                // These appear to be broken in mdbook right now?
                if fragment.starts_with("-") {
                    return;
                }

                let entry = &mut cache.get_mut(&pretty_path).unwrap();
                entry.parse_ids(&pretty_path, &contents, errors);

                if !entry.ids.contains(*fragment) && !is_exception(file, &format!("#{}", fragment))
                {
                    *errors = true;
                    print!("{}:{}: broken link fragment ", pretty_file.display(), i + 1);
                    println!("`#{}` pointing to `{}`", fragment, pretty_path.display());
                };
            }
        } else {
            let pretty_path = path.strip_prefix(root).unwrap_or(&path);
            if !is_exception(file, pretty_path.to_str().unwrap()) {
                *errors = true;
                print!("{}:{}: broken link - ", pretty_file.display(), i + 1);
                println!("{}", pretty_path.display());
            }
        }
    });
    Some(pretty_file)
}

fn load_file(
    cache: &mut Cache,
    root: &Path,
    file: &Path,
    redirect: Redirect,
) -> Result<(PathBuf, Rc<String>), LoadError> {
    let pretty_file = PathBuf::from(file.strip_prefix(root).unwrap_or(&file));

    let (maybe_redirect, contents) = match cache.entry(pretty_file.clone()) {
        Entry::Occupied(entry) => (None, entry.get().source.clone()),
        Entry::Vacant(entry) => {
            let contents = match fs::read_to_string(file) {
                Ok(s) => Rc::new(s),
                Err(err) => {
                    return Err(if let FromRedirect(true) = redirect {
                        LoadError::BrokenRedirect(file.to_path_buf(), err)
                    } else {
                        LoadError::IOError(err)
                    });
                }
            };

            let maybe = maybe_redirect(&contents);
            if maybe.is_some() {
                if let SkipRedirect = redirect {
                    return Err(LoadError::IsRedirect);
                }
            } else {
                entry.insert(FileEntry { source: contents.clone(), ids: HashSet::new() });
            }
            (maybe, contents)
        }
    };
    match maybe_redirect.map(|url| file.parent().unwrap().join(url)) {
        Some(redirect_file) => load_file(cache, root, &redirect_file, FromRedirect(true)),
        None => Ok((pretty_file, contents)),
    }
}

fn maybe_redirect(source: &str) -> Option<String> {
    const REDIRECT: &'static str = "
Redirecting to <a href=";

    let mut lines = source.lines();
    let redirect_line = lines.nth(6)?;

    redirect_line.find(REDIRECT).map(|i| {
        let rest = &redirect_line[(i + REDIRECT.len() + 1)..];
        let pos_quote = rest.find('"').unwrap();
        rest[..pos_quote].to_owned()
    })
}

fn with_attrs_in_source<F: FnMut(&str, usize, &str)>(contents: &str, attr: &str, mut f: F) {
    let mut base = "";
    for (i, mut line) in contents.lines().enumerate() {
        while let Some(j) = line.find(attr) {
            let rest = &line[j + attr.len()..];
            // The base tag should always be the first link in the document so
            // we can get away with using one pass.
            let is_base = line[..j].ends_with("<base");
            line = rest;
            let pos_equals = match rest.find("=") {
                Some(i) => i,
                None => continue,
            };
            if rest[..pos_equals].trim_start_matches(" ") != "" {
                continue;
            }

            let rest = &rest[pos_equals + 1..];

            let pos_quote = match rest.find(&['"', '\''][..]) {
                Some(i) => i,
                None => continue,
            };
            let quote_delim = rest.as_bytes()[pos_quote] as char;

            if rest[..pos_quote].trim_start_matches(" ") != "" {
                continue;
            }
            let rest = &rest[pos_quote + 1..];
            let url = match rest.find(quote_delim) {
                Some(i) => &rest[..i],
                None => continue,
            };
            if is_base {
                base = url;
                continue;
            }
            f(url, i, base)
        }
    }
}
<|endoftext|>


<commit_before>use sass::rule::SassRule;
use sass::variable::SassVariable;

use std::borrow::Cow;

#[derive(Debug, Clone)]
pub enum Event<'a> {
    Property(Cow<'a, str>, Cow<'a, str>),
    Comment(Cow<'a, str>),
    ChildRule(SassRule<'a>),
    Variable(SassVariable<'a>),
}

impl<'a> Event<'a> {
    pub fn expanded(&self) -> String {
        match *self {
            Event::Property(ref name, ref value) => format!("  {}: {};", name, value),
            Event::Comment(ref comment) => format!("  {}", comment),
            Event::ChildRule(ref sass_rule) => sass_rule.expanded(),
            Event::Variable(..) => unreachable!(),
        }
    }

    pub fn nested(&self) -> String {
        match *self {
            Event::Property(ref name, ref value) => format!("  {}: {};", name, value),
            Event::Comment(ref comment) => format!("  {}", comment),
            Event::ChildRule(ref sass_rule) => sass_rule.nested(),
            Event::Variable(..) => unreachable!(),
        }
    }

    pub fn compact(&self) -> String {
        match *self {
            Event::Property(ref name, ref value) => format!("{}: {};", name, value),
            Event::Comment(ref comment) => (*comment).to_string(),
            Event::ChildRule(ref sass_rule) => sass_rule.compact(),
            Event::Variable(..) => unreachable!(),
        }
    }

    pub fn compressed(&self) -> String {
        match *self {
            Event::Property(ref name, ref value) => format!("{}:{}", name, value),
            Event::Comment(..) => unreachable!(),
            Event::ChildRule(ref sass_rule) => sass_rule.compressed(),
            Event::Variable(..) => unreachable!(),
        }
    }

    pub fn is_child_rule(&self) -> bool {
        match self {
            &Event::ChildRule(..) => true,
            _ => false,
        }
    }

    pub fn is_comment(&self) -> bool {
        match self {
            &Event::Comment(..) => true,
            _ => false,
        }
    }
}
<commit_msg>Remove space after commas in compressed output<commit_after>use sass::rule::SassRule;
use sass::variable::SassVariable;

use std::borrow::Cow;

#[derive(Debug, Clone)]
pub enum Event<'a> {
    Property(Cow<'a, str>, Cow<'a, str>),
    Comment(Cow<'a, str>),
    ChildRule(SassRule<'a>),
    Variable(SassVariable<'a>),
}

impl<'a> Event<'a> {
    pub fn expanded(&self) -> String {
        match *self {
            Event::Property(ref name, ref value) => format!("  {}: {};", name, value),
            Event::Comment(ref comment) => format!("  {}", comment),
            Event::ChildRule(ref sass_rule) => sass_rule.expanded(),
            Event::Variable(..) => unreachable!(),
        }
    }

    pub fn nested(&self) -> String {
        match *self {
            Event::Property(ref name, ref value) => format!("  {}: {};", name, value),
            Event::Comment(ref comment) => format!("  {}", comment),
            Event::ChildRule(ref sass_rule) => sass_rule.nested(),
            Event::Variable(..) => unreachable!(),
        }
    }

    pub fn compact(&self) -> String {
        match *self {
            Event::Property(ref name, ref value) => format!("{}: {};", name, value),
            Event::Comment(ref comment) => (*comment).to_string(),
            Event::ChildRule(ref sass_rule) => sass_rule.compact(),
            Event::Variable(..) => unreachable!(),
        }
    }

    pub fn compressed(&self) -> String {
        match *self {
            Event::Property(ref name, ref value) => {
                format!("{}:{}", name, value.replace(", ", ","))
            },
            Event::Comment(..) => unreachable!(),
            Event::ChildRule(ref sass_rule) => sass_rule.compressed(),
            Event::Variable(..) => unreachable!(),
        }
    }

    pub fn is_child_rule(&self) -> bool {
        match self {
            &Event::ChildRule(..) => true,
            _ => false,
        }
    }

    pub fn is_comment(&self) -> bool {
        match self {
            &Event::Comment(..) => true,
            _ => false,
        }
    }
}
<|endoftext|>


<commit_before>use std::io::{stdout, stderr};
use std::sync::Arc;

use argparse::{ArgumentParser};
use argparse::{List, Store, StoreOption, StoreTrue};
use unshare::{Command};

use capsule::Context;
use capsule::packages::State;
use capsule::download;
use launcher::wrap::Wrapper;
use process_util::{run_and_wait, convert_status};

pub fn run_script(context: &Context, mut args: Vec<String>)
    -> Result<i32, String>
{
    let mut cmdargs = Vec::<String>::new();
    let mut url = "".to_string();
    let mut sha256 = None;
    let mut refresh = false;
    {
        args.insert(0, "vagga _capsule script".to_string());
        let mut ap = ArgumentParser::new();
        ap.set_description("
            Downloads script if not cached, puts it into a cache and starts.
            ");
        ap.refer(&mut sha256)
            .add_option(&["--sha256"], StoreOption,
                "A SHA256 hashsum of a script (if you want to check)");
        ap.refer(&mut refresh)
            .add_option(&["--refresh"], StoreTrue,
                "Download file even if there is a cached item");
        ap.refer(&mut url)
            .add_argument("url", Store,
                "A script to run")
            .required();
        ap.refer(&mut cmdargs)
            .add_argument("arg", List, "Arguments to the script");
        ap.stop_on_first_argument(true);
        match ap.parse(args.clone(), &mut stdout(), &mut stderr()) {
            Ok(()) => {}
            Err(0) => return Ok(0),
            Err(_) => {
                return Ok(122);
            }
        }
    }
    // TODO(tailhook) wrap settings into Arc in the launcher's main
    let mut capsule = State::new(&Arc::new(context.settings.clone()));
    let (path, _) = download::maybe_download_and_check_hashsum(
        &mut capsule, &url, sha256, refresh)?;

    let mut cmd: Command = Command::new("/bin/sh");
    cmd.workdir(&context.workdir);
    cmd.arg(&path);
    cmd.args(&cmdargs);
    let res = run_and_wait(&mut cmd).map(convert_status);

    return res;
}
<commit_msg>The `vagga _capsule script` now runs command directly instead of using shell<commit_after>use std::io::{stdout, stderr};
use std::fs::{set_permissions};
use std::sync::Arc;
use std::os::unix::fs::PermissionsExt;

use argparse::{ArgumentParser};
use argparse::{List, Store, StoreOption, StoreTrue};
use unshare::{Command};

use capsule::Context;
use capsule::packages::State;
use capsule::download;
use launcher::wrap::Wrapper;
use process_util::{run_and_wait, convert_status};

pub fn run_script(context: &Context, mut args: Vec<String>)
    -> Result<i32, String>
{
    let mut cmdargs = Vec::<String>::new();
    let mut url = "".to_string();
    let mut sha256 = None;
    let mut refresh = false;
    {
        args.insert(0, "vagga _capsule script".to_string());
        let mut ap = ArgumentParser::new();
        ap.set_description("
            Downloads script if not cached, puts it into a cache and starts.
            ");
        ap.refer(&mut sha256)
            .add_option(&["--sha256"], StoreOption,
                "A SHA256 hashsum of a script (if you want to check)");
        ap.refer(&mut refresh)
            .add_option(&["--refresh"], StoreTrue,
                "Download file even if there is a cached item");
        ap.refer(&mut url)
            .add_argument("url", Store,
                "A script to run")
            .required();
        ap.refer(&mut cmdargs)
            .add_argument("arg", List, "Arguments to the script");
        ap.stop_on_first_argument(true);
        match ap.parse(args.clone(), &mut stdout(), &mut stderr()) {
            Ok(()) => {}
            Err(0) => return Ok(0),
            Err(_) => {
                return Ok(122);
            }
        }
    }
    // TODO(tailhook) wrap settings into Arc in the launcher's main
    let mut capsule = State::new(&Arc::new(context.settings.clone()));
    let (path, _) = download::maybe_download_and_check_hashsum(
        &mut capsule, &url, sha256, refresh)?;
    set_permissions(&path, PermissionsExt::from_mode(0o755))
        .map_err(|e| format!("can't set permissions for {:?}: {}", path, e))?;

    let mut cmd: Command = Command::new(&path);
    cmd.workdir(&context.workdir);
    cmd.args(&cmdargs);
    let res = run_and_wait(&mut cmd).map(convert_status);

    return res;
}
<|endoftext|>


<commit_before>//! Render to a window created by Glutin, using Glium's OpenGL functions

// FIXME: Support resizing the window, like the SDL renderer does

use super::FrontendAction;
use ppu::{SCREEN_WIDTH, SCREEN_HEIGHT};

use glium::{DisplayBuild, Surface, Rect};
use glium::backend::glutin_backend::GlutinFacade;
use glium::index::{NoIndices, PrimitiveType};
use glium::glutin::WindowBuilder;
use glium::program::Program;
use glium::texture::{ClientFormat, RawImage2d, Texture2d};
use glium::uniforms::MagnifySamplerFilter;
use glium::vertex::VertexBuffer;

use std::borrow::Cow;

#[derive(Copy, Clone)]
struct Vertex {
    position: [f32; 2],
}

impl Vertex {
    fn new(x: f32, y: f32) -> Self { Vertex { position: [x, y] } }
}

implement_vertex!(Vertex, position);

const VERTEX_SHADER_SRC: &'static str = r#"
    #version 140

    in vec2 position;
    out vec2 tex_coords;

    void main() {
        tex_coords = vec2((position.x + 1) * 0.5, 1 - (position.y + 1) * 0.5);
        gl_Position = vec4(position, 0.0, 1.0);
    }
"#;

const FRAGMENT_SHADER_SRC: &'static str = r#"
    #version 140

    in vec2 tex_coords;
    out vec4 color;

    uniform sampler2D tex;

    void main() {
        color = texture(tex, tex_coords);
    }
"#;

pub struct GliumRenderer {
    display: GlutinFacade,
    vbuf: VertexBuffer<Vertex>,
    program: Program,
    texture: Texture2d,
}

impl Default for GliumRenderer {
    fn default() -> Self {
        let display = WindowBuilder::new()
            .with_dimensions(SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3)
            .with_title("sneeze".to_owned())
            .build_glium().unwrap();
        let shape = [
            Vertex::new(-1.0, 1.0),
            Vertex::new(1.0, 1.0),
            Vertex::new(-1.0, -1.0),
            Vertex::new(1.0, -1.0),
        ];

        GliumRenderer {
            vbuf: VertexBuffer::new(&display, &shape).unwrap(),
            program: Program::from_source(&display, VERTEX_SHADER_SRC, FRAGMENT_SHADER_SRC, None)
                .unwrap(),
            texture: Texture2d::empty(&display, SCREEN_WIDTH, SCREEN_HEIGHT)
                .unwrap(),
            display: display,
        }
    }
}

impl GliumRenderer {
    fn handle_events(&mut self) -> Option<FrontendAction> {
        use glium::glutin::Event::*;

        for ev in self.display.poll_events() {
            match ev {
                Closed => {
                    info!("quit event -> exiting");
                    return Some(FrontendAction::Exit)
                }
                _ => {}
            }
        }
        None
    }
}

impl super::Renderer for GliumRenderer {
    fn render(&mut self, frame_data: &[u8]) -> Option<FrontendAction> {
        // upload new texture data
        self.texture.write(Rect {
            left: 0,
            bottom: 0,
            width: SCREEN_WIDTH,
            height: SCREEN_HEIGHT,
        }, RawImage2d {
            data: Cow::Borrowed(frame_data),
            width: SCREEN_WIDTH,
            height: SCREEN_HEIGHT,
            format: ClientFormat::U8U8U8,
        });

        let mut target = self.display.draw();
        target.draw(
            &self.vbuf,
            &NoIndices(PrimitiveType::TriangleStrip),
            &self.program,
            &uniform! {
                tex: self.texture.sampled()
                    .magnify_filter(MagnifySamplerFilter::Nearest),
            },
            &Default::default()).unwrap();
        target.finish().unwrap();

        self.handle_events()
    }
}
<commit_msg>Add a few comments to the glium frontend<commit_after>//! Render to a window created by Glutin, using Glium's OpenGL functions

// FIXME: Support resizing the window, like the SDL renderer does

use super::FrontendAction;
use ppu::{SCREEN_WIDTH, SCREEN_HEIGHT};

use glium::{DisplayBuild, Surface, Rect};
use glium::backend::glutin_backend::GlutinFacade;
use glium::index::{NoIndices, PrimitiveType};
use glium::glutin::WindowBuilder;
use glium::program::Program;
use glium::texture::{ClientFormat, RawImage2d, Texture2d};
use glium::uniforms::MagnifySamplerFilter;
use glium::vertex::VertexBuffer;

use std::borrow::Cow;

/// Our vertices are extremely simple: There's no need for more than 2 dimensions and the texture
/// coordinates can be calculated from the position, so we don't need to store them.
#[derive(Copy, Clone)]
struct Vertex {
    position: [f32; 2],
}

impl Vertex {
    fn new(x: f32, y: f32) -> Self { Vertex { position: [x, y] } }
}

implement_vertex!(Vertex, position);

const VERTEX_SHADER_SRC: &'static str = r#"
    #version 140

    in vec2 position;
    out vec2 tex_coords;

    void main() {
        tex_coords = vec2((position.x + 1) * 0.5, 1 - (position.y + 1) * 0.5);
        gl_Position = vec4(position, 0.0, 1.0);
    }
"#;

const FRAGMENT_SHADER_SRC: &'static str = r#"
    #version 140

    in vec2 tex_coords;
    out vec4 color;

    uniform sampler2D tex;

    void main() {
        color = texture(tex, tex_coords);
    }
"#;

pub struct GliumRenderer {
    display: GlutinFacade,
    /// This vertex buffer will only ever store 4 vertices spanning the whole window
    vbuf: VertexBuffer<Vertex>,
    /// A simple shader that maps our texture onto the window
    program: Program,
    /// This texture is updated with the PPU's data every frame
    texture: Texture2d,
}

impl Default for GliumRenderer {
    fn default() -> Self {
        let display = WindowBuilder::new()
            .with_dimensions(SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3)
            .with_title("sneeze".to_owned())
            .build_glium().unwrap();
        // Create a rectangle spanning the whole viewport/window. This is a triangle strip.
        let shape = [
            Vertex::new(-1.0, 1.0),
            Vertex::new(1.0, 1.0),
            Vertex::new(-1.0, -1.0),
            Vertex::new(1.0, -1.0),
        ];

        GliumRenderer {
            vbuf: VertexBuffer::new(&display, &shape).unwrap(),
            program: Program::from_source(&display, VERTEX_SHADER_SRC, FRAGMENT_SHADER_SRC, None)
                .unwrap(),
            texture: Texture2d::empty(&display, SCREEN_WIDTH, SCREEN_HEIGHT)
                .unwrap(),
            display: display,
        }
    }
}

impl GliumRenderer {
    fn handle_events(&mut self) -> Option<FrontendAction> {
        use glium::glutin::Event::*;

        for ev in self.display.poll_events() {
            match ev {
                Closed => {
                    info!("quit event -> exiting");
                    return Some(FrontendAction::Exit)
                }
                _ => {}
            }
        }
        None
    }
}

impl super::Renderer for GliumRenderer {
    fn render(&mut self, frame_data: &[u8]) -> Option<FrontendAction> {
        // upload new texture data
        self.texture.write(Rect {
            left: 0,
            bottom: 0,
            width: SCREEN_WIDTH,
            height: SCREEN_HEIGHT,
        }, RawImage2d {
            data: Cow::Borrowed(frame_data),
            width: SCREEN_WIDTH,
            height: SCREEN_HEIGHT,
            format: ClientFormat::U8U8U8,
        });

        let mut target = self.display.draw();
        target.draw(
            &self.vbuf,
            &NoIndices(PrimitiveType::TriangleStrip),
            &self.program,
            &uniform! {
                tex: self.texture.sampled()
                    .magnify_filter(MagnifySamplerFilter::Nearest),
            },
            &Default::default()).unwrap();
        target.finish().unwrap();

        self.handle_events()
    }
}
<|endoftext|>


<commit_before>use crate::internal::{self, consts, DirEntry, ObjType};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use uuid::Uuid;

// ========================================================================= //

/// Metadata about a single object (storage or stream) in a compound file.
#[derive(Clone)]
pub struct Entry {
    name: String,
    path: PathBuf,
    obj_type: ObjType,
    clsid: Uuid,
    state_bits: u32,
    creation_time: u64,
    modified_time: u64,
    stream_len: u64,
}

impl Entry {
    pub(crate) fn new(dir_entry: &DirEntry, path: PathBuf) -> Entry {
        Entry {
            name: dir_entry.name.clone(),
            path,
            obj_type: dir_entry.obj_type,
            clsid: dir_entry.clsid,
            state_bits: dir_entry.state_bits,
            creation_time: dir_entry.creation_time,
            modified_time: dir_entry.modified_time,
            stream_len: dir_entry.stream_len,
        }
    }

    /// Returns the name of the object that this entry represents.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Returns the full path to the object that this entry represents.
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Returns whether this entry is for a stream object (i.e. a "file" within
    /// the compound file).
    pub fn is_stream(&self) -> bool {
        self.obj_type == ObjType::Stream
    }

    /// Returns whether this entry is for a storage object (i.e. a "directory"
    /// within the compound file), either the root or a nested storage.
    pub fn is_storage(&self) -> bool {
        self.obj_type == ObjType::Storage || self.obj_type == ObjType::Root
    }

    /// Returns whether this entry is specifically for the root storage object
    /// of the compound file.
    pub fn is_root(&self) -> bool {
        self.obj_type == ObjType::Root
    }

    /// Returns the size, in bytes, of the stream that this metadata is for.
    pub fn len(&self) -> u64 {
        self.stream_len
    }

    /// Returns stream is empty
    pub fn is_empty(&self) -> bool {
        self.stream_len == 0
    }

    /// Returns the CLSID (that is, the object class GUID) for this object.
    /// This will always be all zeros for stream objects.
    pub fn clsid(&self) -> &Uuid {
        &self.clsid
    }

    /// Returns the user-defined bitflags set for this object.
    pub fn state_bits(&self) -> u32 {
        self.state_bits
    }

    /// Returns the time when the object that this entry represents was
    /// created.
    pub fn created(&self) -> SystemTime {
        internal::time::system_time_from_timestamp(self.creation_time)
    }

    /// Returns the time when the object that this entry represents was last
    /// modified.
    pub fn modified(&self) -> SystemTime {
        internal::time::system_time_from_timestamp(self.modified_time)
    }
}

// ========================================================================= //

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum EntriesOrder {
    Nonrecursive,
    Preorder,
}

// ========================================================================= //

/// An iterator over the entries in a storage object.
pub struct Entries<'a> {
    order: EntriesOrder,
    directory: &'a [DirEntry],
    stack: Vec<(PathBuf, u32, bool)>,
}

impl<'a> Entries<'a> {
    pub(crate) fn new(
        order: EntriesOrder,
        directory: &'a [DirEntry],
        parent_path: PathBuf,
        start: u32,
    ) -> Entries<'a> {
        let mut entries = Entries { order, directory, stack: Vec::new() };
        match order {
            EntriesOrder::Nonrecursive => {
                entries.stack_left_spine(&parent_path, start);
            }
            EntriesOrder::Preorder => {
                entries.stack.push((parent_path, start, false));
            }
        }
        entries
    }

    fn join_path(parent_path: &Path, dir_entry: &DirEntry) -> PathBuf {
        if dir_entry.obj_type == ObjType::Root {
            parent_path.to_path_buf()
        } else {
            parent_path.join(&dir_entry.name)
        }
    }

    fn stack_left_spine(&mut self, parent_path: &Path, mut current_id: u32) {
        while current_id != consts::NO_STREAM {
            let dir_entry = &self.directory[current_id as usize];
            self.stack.push((parent_path.to_path_buf(), current_id, true));
            current_id = dir_entry.left_sibling;
        }
    }
}

impl<'a> Iterator for Entries<'a> {
    type Item = Entry;

    fn next(&mut self) -> Option<Entry> {
        if let Some((parent, stream_id, visit_siblings)) = self.stack.pop() {
            let dir_entry = &self.directory[stream_id as usize];
            let path = Entries::join_path(&parent, dir_entry);
            if visit_siblings {
                self.stack_left_spine(&parent, dir_entry.right_sibling);
            }
            if self.order == EntriesOrder::Preorder
                && dir_entry.obj_type != ObjType::Stream
                && dir_entry.child != consts::NO_STREAM
            {
                self.stack_left_spine(&path, dir_entry.child);
            }
            Some(Entry::new(dir_entry, path))
        } else {
            None
        }
    }
}

// ========================================================================= //

#[cfg(test)]
mod tests {
    use super::{Entries, EntriesOrder, Entry};
    use crate::internal::consts::{NO_STREAM, ROOT_DIR_NAME};
    use crate::internal::{DirEntry, ObjType};
    use std::path::PathBuf;

    fn make_entry(
        name: &str,
        obj_type: ObjType,
        left: u32,
        child: u32,
        right: u32,
    ) -> DirEntry {
        let mut dir_entry = DirEntry::new(name, obj_type, 0);
        dir_entry.left_sibling = left;
        dir_entry.child = child;
        dir_entry.right_sibling = right;
        dir_entry
    }

    fn make_directory() -> Vec<DirEntry> {
        // Root contains:      3 contains:
        //      5                  8
        //     / \                / \
        //    3   6              7   9
        //   / \
        //  1   4
        //   \
        //    2
        vec![
            make_entry(ROOT_DIR_NAME, ObjType::Root, NO_STREAM, 5, NO_STREAM),
            make_entry("1", ObjType::Stream, NO_STREAM, NO_STREAM, 2),
            make_entry("2", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
            make_entry("3", ObjType::Storage, 1, 8, 4),
            make_entry("4", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
            make_entry("5", ObjType::Stream, 3, NO_STREAM, 6),
            make_entry("6", ObjType::Storage, NO_STREAM, NO_STREAM, NO_STREAM),
            make_entry("7", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
            make_entry("8", ObjType::Stream, 7, NO_STREAM, 9),
            make_entry("9", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
        ]
    }

    fn paths_for_entries<'a>(entries: &'a [Entry]) -> Vec<&'a str> {
        entries.iter().map(|entry| entry.path().to_str().unwrap()).collect()
    }

    #[test]
    fn nonrecursive_entries_from_root() {
        let directory = make_directory();
        let entries: Vec<Entry> = Entries::new(
            EntriesOrder::Nonrecursive,
            &directory,
            PathBuf::from("/"),
            5,
        )
        .collect();
        let paths = paths_for_entries(&entries);
        assert_eq!(paths, vec!["/1", "/2", "/3", "/4", "/5", "/6"]);
    }

    #[test]
    fn nonrecursive_entries_from_storage() {
        let directory = make_directory();
        let entries: Vec<Entry> = Entries::new(
            EntriesOrder::Nonrecursive,
            &directory,
            PathBuf::from("/3"),
            8,
        )
        .collect();
        let paths = paths_for_entries(&entries);
        assert_eq!(paths, vec!["/3/7", "/3/8", "/3/9"]);
    }

    #[test]
    fn preorder_entries_from_root() {
        let directory = make_directory();
        let entries: Vec<Entry> = Entries::new(
            EntriesOrder::Preorder,
            &directory,
            PathBuf::from("/"),
            0,
        )
        .collect();
        let paths = paths_for_entries(&entries);
        assert_eq!(
            paths,
            vec![
                "/", "/1", "/2", "/3", "/3/7", "/3/8", "/3/9", "/4", "/5",
                "/6",
            ]
        );
    }

    #[test]
    fn preorder_entries_from_storage() {
        let directory = make_directory();
        let entries: Vec<Entry> = Entries::new(
            EntriesOrder::Preorder,
            &directory,
            PathBuf::from("/"),
            3,
        )
        .collect();
        let paths = paths_for_entries(&entries);
        assert_eq!(paths, vec!["/3", "/3/7", "/3/8", "/3/9"]);
    }
}

// ========================================================================= //
<commit_msg>Replace &str to &Path<commit_after>use crate::internal::{self, consts, DirEntry, ObjType};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use uuid::Uuid;

// ========================================================================= //

/// Metadata about a single object (storage or stream) in a compound file.
#[derive(Clone)]
pub struct Entry {
    name: String,
    path: PathBuf,
    obj_type: ObjType,
    clsid: Uuid,
    state_bits: u32,
    creation_time: u64,
    modified_time: u64,
    stream_len: u64,
}

impl Entry {
    pub(crate) fn new(dir_entry: &DirEntry, path: PathBuf) -> Entry {
        Entry {
            name: dir_entry.name.clone(),
            path,
            obj_type: dir_entry.obj_type,
            clsid: dir_entry.clsid,
            state_bits: dir_entry.state_bits,
            creation_time: dir_entry.creation_time,
            modified_time: dir_entry.modified_time,
            stream_len: dir_entry.stream_len,
        }
    }

    /// Returns the name of the object that this entry represents.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Returns the full path to the object that this entry represents.
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Returns whether this entry is for a stream object (i.e. a "file" within
    /// the compound file).
    pub fn is_stream(&self) -> bool {
        self.obj_type == ObjType::Stream
    }

    /// Returns whether this entry is for a storage object (i.e. a "directory"
    /// within the compound file), either the root or a nested storage.
    pub fn is_storage(&self) -> bool {
        self.obj_type == ObjType::Storage || self.obj_type == ObjType::Root
    }

    /// Returns whether this entry is specifically for the root storage object
    /// of the compound file.
    pub fn is_root(&self) -> bool {
        self.obj_type == ObjType::Root
    }

    /// Returns the size, in bytes, of the stream that this metadata is for.
    pub fn len(&self) -> u64 {
        self.stream_len
    }

    /// Returns stream is empty
    pub fn is_empty(&self) -> bool {
        self.stream_len == 0
    }

    /// Returns the CLSID (that is, the object class GUID) for this object.
    /// This will always be all zeros for stream objects.
    pub fn clsid(&self) -> &Uuid {
        &self.clsid
    }

    /// Returns the user-defined bitflags set for this object.
    pub fn state_bits(&self) -> u32 {
        self.state_bits
    }

    /// Returns the time when the object that this entry represents was
    /// created.
    pub fn created(&self) -> SystemTime {
        internal::time::system_time_from_timestamp(self.creation_time)
    }

    /// Returns the time when the object that this entry represents was last
    /// modified.
    pub fn modified(&self) -> SystemTime {
        internal::time::system_time_from_timestamp(self.modified_time)
    }
}

// ========================================================================= //

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum EntriesOrder {
    Nonrecursive,
    Preorder,
}

// ========================================================================= //

/// An iterator over the entries in a storage object.
pub struct Entries<'a> {
    order: EntriesOrder,
    directory: &'a [DirEntry],
    stack: Vec<(PathBuf, u32, bool)>,
}

impl<'a> Entries<'a> {
    pub(crate) fn new(
        order: EntriesOrder,
        directory: &'a [DirEntry],
        parent_path: PathBuf,
        start: u32,
    ) -> Entries<'a> {
        let mut entries = Entries { order, directory, stack: Vec::new() };
        match order {
            EntriesOrder::Nonrecursive => {
                entries.stack_left_spine(&parent_path, start);
            }
            EntriesOrder::Preorder => {
                entries.stack.push((parent_path, start, false));
            }
        }
        entries
    }

    fn join_path(parent_path: &Path, dir_entry: &DirEntry) -> PathBuf {
        if dir_entry.obj_type == ObjType::Root {
            parent_path.to_path_buf()
        } else {
            parent_path.join(&dir_entry.name)
        }
    }

    fn stack_left_spine(&mut self, parent_path: &Path, mut current_id: u32) {
        while current_id != consts::NO_STREAM {
            let dir_entry = &self.directory[current_id as usize];
            self.stack.push((parent_path.to_path_buf(), current_id, true));
            current_id = dir_entry.left_sibling;
        }
    }
}

impl<'a> Iterator for Entries<'a> {
    type Item = Entry;

    fn next(&mut self) -> Option<Entry> {
        if let Some((parent, stream_id, visit_siblings)) = self.stack.pop() {
            let dir_entry = &self.directory[stream_id as usize];
            let path = Entries::join_path(&parent, dir_entry);
            if visit_siblings {
                self.stack_left_spine(&parent, dir_entry.right_sibling);
            }
            if self.order == EntriesOrder::Preorder
                && dir_entry.obj_type != ObjType::Stream
                && dir_entry.child != consts::NO_STREAM
            {
                self.stack_left_spine(&path, dir_entry.child);
            }
            Some(Entry::new(dir_entry, path))
        } else {
            None
        }
    }
}

// ========================================================================= //

#[cfg(test)]
mod tests {
    use super::{Entries, EntriesOrder, Entry};
    use crate::internal::consts::{NO_STREAM, ROOT_DIR_NAME};
    use crate::internal::{DirEntry, ObjType};
    use std::path::{Path, PathBuf};

    fn make_entry(
        name: &str,
        obj_type: ObjType,
        left: u32,
        child: u32,
        right: u32,
    ) -> DirEntry {
        let mut dir_entry = DirEntry::new(name, obj_type, 0);
        dir_entry.left_sibling = left;
        dir_entry.child = child;
        dir_entry.right_sibling = right;
        dir_entry
    }

    fn make_directory() -> Vec<DirEntry> {
        // Root contains:      3 contains:
        //      5                  8
        //     / \                / \
        //    3   6              7   9
        //   / \
        //  1   4
        //   \
        //    2
        vec![
            make_entry(ROOT_DIR_NAME, ObjType::Root, NO_STREAM, 5, NO_STREAM),
            make_entry("1", ObjType::Stream, NO_STREAM, NO_STREAM, 2),
            make_entry("2", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
            make_entry("3", ObjType::Storage, 1, 8, 4),
            make_entry("4", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
            make_entry("5", ObjType::Stream, 3, NO_STREAM, 6),
            make_entry("6", ObjType::Storage, NO_STREAM, NO_STREAM, NO_STREAM),
            make_entry("7", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
            make_entry("8", ObjType::Stream, 7, NO_STREAM, 9),
            make_entry("9", ObjType::Stream, NO_STREAM, NO_STREAM, NO_STREAM),
        ]
    }

    fn paths_for_entries(entries: &[Entry]) -> Vec<&Path> {
        entries.iter().map(|entry| entry.path()).collect()
    }

    #[test]
    fn nonrecursive_entries_from_root() {
        let directory = make_directory();
        let entries: Vec<Entry> = Entries::new(
            EntriesOrder::Nonrecursive,
            &directory,
            PathBuf::from("/"),
            5,
        )
        .collect();
        let paths = paths_for_entries(&entries);
        assert_eq!(
            paths,
            vec![
                Path::new("/1"),
                Path::new("/2"),
                Path::new("/3"),
                Path::new("/4"),
                Path::new("/5"),
                Path::new("/6")
            ]
        );
    }

    #[test]
    fn nonrecursive_entries_from_storage() {
        let directory = make_directory();
        let entries: Vec<Entry> = Entries::new(
            EntriesOrder::Nonrecursive,
            &directory,
            PathBuf::from("/3"),
            8,
        )
        .collect();
        let paths = paths_for_entries(&entries);
        assert_eq!(
            paths,
            vec![Path::new("/3/7"), Path::new("/3/8"), Path::new("/3/9")]
        );
    }

    #[test]
    fn preorder_entries_from_root() {
        let directory = make_directory();
        let entries: Vec<Entry> = Entries::new(
            EntriesOrder::Preorder,
            &directory,
            PathBuf::from("/"),
            0,
        )
        .collect();
        let paths = paths_for_entries(&entries);
        assert_eq!(
            paths,
            vec![
                Path::new("/"),
                Path::new("/1"),
                Path::new("/2"),
                Path::new("/3"),
                Path::new("/3/7"),
                Path::new("/3/8"),
                Path::new("/3/9"),
                Path::new("/4"),
                Path::new("/5"),
                Path::new("/6"),
            ]
        );
    }

    #[test]
    fn preorder_entries_from_storage() {
        let directory = make_directory();
        let entries: Vec<Entry> = Entries::new(
            EntriesOrder::Preorder,
            &directory,
            PathBuf::from("/"),
            3,
        )
        .collect();
        let paths = paths_for_entries(&entries);
        assert_eq!(
            paths,
            vec![
                Path::new("/3"),
                Path::new("/3/7"),
                Path::new("/3/8"),
                Path::new("/3/9")
            ]
        );
    }
}

// ========================================================================= //
<|endoftext|>


<commit_before>use crate::cached_size::CachedSize;
use crate::UnknownFields;

/// Special fields included in each generated message.
#[derive(Default, Eq, PartialEq, Clone, Debug)]
pub struct SpecialFields {
    unknown_fields: UnknownFields,
    cached_size: CachedSize,
}

impl SpecialFields {
    /// Defaults.
    pub const fn new() -> SpecialFields {
        SpecialFields {
            unknown_fields: UnknownFields::new(),
            cached_size: CachedSize::new(),
        }
    }

    /// Clear.
    pub fn clear(&mut self) {
        self.unknown_fields.clear();
        // No need to clear `cached_size`.
    }

    /// Getter.
    pub fn cached_size(&self) -> &CachedSize {
        &self.cached_size
    }

    /// Getter.
    pub fn unknown_fields(&self) -> &UnknownFields {
        &self.unknown_fields
    }

    /// Setter.
    pub fn mut_unknown_fields(&mut self) -> &mut UnknownFields {
        &mut self.unknown_fields
    }
}
<commit_msg>Hash for SpecialFields<commit_after>use crate::cached_size::CachedSize;
use crate::UnknownFields;

/// Special fields included in each generated message.
#[derive(Default, Eq, PartialEq, Clone, Debug, Hash)]
pub struct SpecialFields {
    unknown_fields: UnknownFields,
    cached_size: CachedSize,
}

impl SpecialFields {
    /// Defaults.
    pub const fn new() -> SpecialFields {
        SpecialFields {
            unknown_fields: UnknownFields::new(),
            cached_size: CachedSize::new(),
        }
    }

    /// Clear.
    pub fn clear(&mut self) {
        self.unknown_fields.clear();
        // No need to clear `cached_size`.
    }

    /// Getter.
    pub fn cached_size(&self) -> &CachedSize {
        &self.cached_size
    }

    /// Getter.
    pub fn unknown_fields(&self) -> &UnknownFields {
        &self.unknown_fields
    }

    /// Setter.
    pub fn mut_unknown_fields(&mut self) -> &mut UnknownFields {
        &mut self.unknown_fields
    }
}
<|endoftext|>


<commit_before>use super::ASTNode;
use seax::compiler_tools::ForkTable;

pub struct Scoped<'a, T> where T: ASTNode {
    node: T, scope: ForkTable<'a, String, Annotation>
}

pub struct Annotation;
<commit_msg>Begin on type structures<commit_after>use super::ASTNode;
use seax::compiler_tools::ForkTable;

pub struct Scoped<'a, T> where T: ASTNode {
    node: T, scope: ForkTable<'a, String, Type>
}

#[derive(Debug, Clone)]
pub enum Type { Ref(Reference)
              , Prim(Primitive)
              , Algebraic //TODO: make me a thing
              }

/// Reference types
///
/// TODO: how will lifetime analysis actually work?
#[derive(Debug, Clone)]
pub enum Reference { Borrowed(Box<Type>) // TODO: borrowed from where?
                   , Moved(Box<Type>) // TODO: moved from where?
                   , Owned(Box<Type>)
                   , Raw(Box<Type>)
                   }

#[derive(Debug, Clone, Copy)]
pub enum Primitive { Int, Uint, Byte, Str // TODO: finish
}
<|endoftext|>


<commit_before>use alloc::arc::{Arc, Weak};
use collections::BTreeMap;
use core::sync::atomic::{AtomicU64, Ordering};
use core::{mem, slice, usize};
use spin::{Mutex, RwLock};

use arch;
use arch::paging::{InactivePageTable, Page, VirtualAddress, entry};
use arch::paging::temporary_page::TemporaryPage;
use context::{self, Context};
use context::memory::Grant;
use scheme::{AtomicSchemeId, ATOMIC_SCHEMEID_INIT, SchemeId};
use sync::{WaitQueue, WaitMap};
use syscall::data::{Packet, Stat};
use syscall::error::*;
use syscall::flag::{EVENT_READ, O_NONBLOCK};
use syscall::number::*;
use syscall::scheme::Scheme;

pub struct UserInner {
    root_id: SchemeId,
    handle_id: usize,
    flags: usize,
    pub scheme_id: AtomicSchemeId,
    next_id: AtomicU64,
    context: Weak<RwLock<Context>>,
    todo: WaitQueue<Packet>,
    fmap: Mutex<BTreeMap<u64, (Weak<RwLock<Context>>, usize)>>,
    done: WaitMap<u64, usize>
}

impl UserInner {
    pub fn new(root_id: SchemeId, handle_id: usize, flags: usize, context: Weak<RwLock<Context>>) -> UserInner {
        UserInner {
            root_id: root_id,
            handle_id: handle_id,
            flags: flags,
            scheme_id: ATOMIC_SCHEMEID_INIT,
            next_id: AtomicU64::new(1),
            context: context,
            todo: WaitQueue::new(),
            fmap: Mutex::new(BTreeMap::new()),
            done: WaitMap::new()
        }
    }

    pub fn call(&self, a: usize, b: usize, c: usize, d: usize) -> Result<usize> {
        let (pid, uid, gid) = {
            let contexts = context::contexts();
            let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
            let context = context_lock.read();
            (context.id, context.euid, context.egid)
        };

        self.call_inner(Packet {
            id: self.next_id.fetch_add(1, Ordering::SeqCst),
            pid: pid.into(),
            uid: uid,
            gid: gid,
            a: a,
            b: b,
            c: c,
            d: d
        })
    }

    fn call_inner(&self, packet: Packet) -> Result<usize> {
        let id = packet.id;

        let len = self.todo.send(packet);
        context::event::trigger(self.root_id, self.handle_id, EVENT_READ, mem::size_of::<Packet>() * len);

        Error::demux(self.done.receive(&id))
    }

    pub fn capture(&self, buf: &[u8]) -> Result<usize> {
        UserInner::capture_inner(&self.context, buf.as_ptr() as usize, buf.len(), false)
    }

    pub fn capture_mut(&self, buf: &mut [u8]) -> Result<usize> {
        UserInner::capture_inner(&self.context, buf.as_mut_ptr() as usize, buf.len(), true)
    }

    fn capture_inner(context_weak: &Weak<RwLock<Context>>, address: usize, size: usize, writable: bool) -> Result<usize> {
        if size == 0 {
            Ok(0)
        } else {
            let context_lock = context_weak.upgrade().ok_or(Error::new(ESRCH))?;
            let context = context_lock.read();

            let mut grants = context.grants.lock();

            let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_table()) };
            let mut temporary_page = TemporaryPage::new(Page::containing_address(VirtualAddress::new(arch::USER_TMP_GRANT_OFFSET)));

            let from_address = (address/4096) * 4096;
            let offset = address - from_address;
            let full_size = ((offset + size + 4095)/4096) * 4096;
            let mut to_address = arch::USER_GRANT_OFFSET;

            let mut flags = entry::PRESENT | entry::NO_EXECUTE | entry::USER_ACCESSIBLE;
            if writable {
                flags |= entry::WRITABLE;
            }

            for i in 0 .. grants.len() {
                let start = grants[i].start_address().get();
                if to_address + full_size < start {
                    grants.insert(i, Grant::map_inactive(
                        VirtualAddress::new(from_address),
                        VirtualAddress::new(to_address),
                        full_size,
                        flags,
                        &mut new_table,
                        &mut temporary_page
                    ));

                    return Ok(to_address + offset);
                } else {
                    let pages = (grants[i].size() + 4095) / 4096;
                    let end = start + pages * 4096;
                    to_address = end;
                }
            }

            grants.push(Grant::map_inactive(
                VirtualAddress::new(from_address),
                VirtualAddress::new(to_address),
                full_size,
                flags,
                &mut new_table,
                &mut temporary_page
            ));

            Ok(to_address + offset)
        }
    }

    pub fn release(&self, address: usize) -> Result<()> {
        if address == 0 {
            Ok(())
        } else {
            let context_lock = self.context.upgrade().ok_or(Error::new(ESRCH))?;
            let context = context_lock.read();

            let mut grants = context.grants.lock();

            let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_table()) };
            let mut temporary_page = TemporaryPage::new(Page::containing_address(VirtualAddress::new(arch::USER_TMP_GRANT_OFFSET)));

            for i in 0 .. grants.len() {
                let start = grants[i].start_address().get();
                let end = start + grants[i].size();
                if address >= start && address < end {
                    grants.remove(i).unmap_inactive(&mut new_table, &mut temporary_page);

                    return Ok(());
                }
            }

            Err(Error::new(EFAULT))
        }
    }

    pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
        let packet_buf = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut Packet, buf.len()/mem::size_of::<Packet>()) };
        Ok(self.todo.receive_into(packet_buf, self.flags & O_NONBLOCK != O_NONBLOCK) * mem::size_of::<Packet>())
    }

    pub fn write(&self, buf: &[u8]) -> Result<usize> {
        let packet_size = mem::size_of::<Packet>();
        let len = buf.len()/packet_size;
        let mut i = 0;
        while i < len {
            let mut packet = unsafe { *(buf.as_ptr() as *const Packet).offset(i as isize) };
            if packet.id == 0 {
                match packet.a {
                    SYS_FEVENT => context::event::trigger(self.scheme_id.load(Ordering::SeqCst), packet.b, packet.c, packet.d),
                    _ => println!("Unknown scheme -> kernel message {}", packet.a)
                }
            } else {
                if let Some((context_weak, size)) = self.fmap.lock().remove(&packet.id) {
                    if let Ok(address) = Error::demux(packet.a) {
                        packet.a = Error::mux(UserInner::capture_inner(&context_weak, address, size, true));
                    }
                }

                self.done.send(packet.id, packet.a);
            }
            i += 1;
        }

        Ok(i * packet_size)
    }

    pub fn fevent(&self, _flags: usize) -> Result<usize> {
        Ok(self.handle_id)
    }

    pub fn fsync(&self) -> Result<usize> {
        Ok(0)
    }
}

/// UserInner has to be wrapped
pub struct UserScheme {
    inner: Weak<UserInner>
}

impl UserScheme {
    pub fn new(inner: Weak<UserInner>) -> UserScheme {
        UserScheme {
            inner: inner
        }
    }
}

impl Scheme for UserScheme {
    fn open(&self, path: &[u8], flags: usize, _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_OPEN, address, path.len(), flags);
        let _ = inner.release(address);
        result
    }

    fn mkdir(&self, path: &[u8], mode: u16, _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_MKDIR, address, path.len(), mode as usize);
        let _ = inner.release(address);
        result
    }

    fn chmod(&self, path: &[u8], mode: u16, _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_CHMOD, address, path.len(), mode as usize);
        let _ = inner.release(address);
        result
    }

    fn rmdir(&self, path: &[u8], _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_RMDIR, address, path.len(), 0);
        let _ = inner.release(address);
        result
    }

    fn unlink(&self, path: &[u8], _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_UNLINK, address, path.len(), 0);
        let _ = inner.release(address);
        result
    }

    fn dup(&self, file: usize, buf: &[u8]) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(buf)?;
        let result = inner.call(SYS_DUP, file, address, buf.len());
        let _ = inner.release(address);
        result
    }

    fn read(&self, file: usize, buf: &mut [u8]) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture_mut(buf)?;
        let result = inner.call(SYS_READ, file, address, buf.len());
        let _ = inner.release(address);
        result
    }

    fn write(&self, file: usize, buf: &[u8]) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(buf)?;
        let result = inner.call(SYS_WRITE, file, address, buf.len());
        let _ = inner.release(address);
        result
    }

    fn seek(&self, file: usize, position: usize, whence: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_LSEEK, file, position, whence)
    }

    fn fcntl(&self, file: usize, cmd: usize, arg: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_FCNTL, file, cmd, arg)
    }

    fn fevent(&self, file: usize, flags: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_FEVENT, file, flags, 0)
    }

    fn fmap(&self, file: usize, offset: usize, size: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;

        let (pid, uid, gid, context_lock) = {
            let contexts = context::contexts();
            let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
            let context = context_lock.read();
            (context.id, context.euid, context.egid, Arc::downgrade(&context_lock))
        };

        let id = inner.next_id.fetch_add(1, Ordering::SeqCst);

        inner.fmap.lock().insert(id, (context_lock, size));

        inner.call_inner(Packet {
            id: id,
            pid: pid.into(),
            uid: uid,
            gid: gid,
            a: SYS_FMAP,
            b: file,
            c: offset,
            d: size
        })
    }

    fn fpath(&self, file: usize, buf: &mut [u8]) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture_mut(buf)?;
        let result = inner.call(SYS_FPATH, file, address, buf.len());
        let _ = inner.release(address);
        result
    }

    fn fstat(&self, file: usize, stat: &mut Stat) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture_mut(stat)?;
        let result = inner.call(SYS_FSTAT, file, address, 0);
        let _ = inner.release(address);
        result
    }

    fn fsync(&self, file: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_FSYNC, file, 0, 0)
    }

    fn ftruncate(&self, file: usize, len: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_FTRUNCATE, file, len, 0)
    }

    fn close(&self, file: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_CLOSE, file, 0, 0)
    }
}
<commit_msg>Add statvfs<commit_after>use alloc::arc::{Arc, Weak};
use collections::BTreeMap;
use core::sync::atomic::{AtomicU64, Ordering};
use core::{mem, slice, usize};
use spin::{Mutex, RwLock};

use arch;
use arch::paging::{InactivePageTable, Page, VirtualAddress, entry};
use arch::paging::temporary_page::TemporaryPage;
use context::{self, Context};
use context::memory::Grant;
use scheme::{AtomicSchemeId, ATOMIC_SCHEMEID_INIT, SchemeId};
use sync::{WaitQueue, WaitMap};
use syscall::data::{Packet, Stat, StatVfs};
use syscall::error::*;
use syscall::flag::{EVENT_READ, O_NONBLOCK};
use syscall::number::*;
use syscall::scheme::Scheme;

pub struct UserInner {
    root_id: SchemeId,
    handle_id: usize,
    flags: usize,
    pub scheme_id: AtomicSchemeId,
    next_id: AtomicU64,
    context: Weak<RwLock<Context>>,
    todo: WaitQueue<Packet>,
    fmap: Mutex<BTreeMap<u64, (Weak<RwLock<Context>>, usize)>>,
    done: WaitMap<u64, usize>
}

impl UserInner {
    pub fn new(root_id: SchemeId, handle_id: usize, flags: usize, context: Weak<RwLock<Context>>) -> UserInner {
        UserInner {
            root_id: root_id,
            handle_id: handle_id,
            flags: flags,
            scheme_id: ATOMIC_SCHEMEID_INIT,
            next_id: AtomicU64::new(1),
            context: context,
            todo: WaitQueue::new(),
            fmap: Mutex::new(BTreeMap::new()),
            done: WaitMap::new()
        }
    }

    pub fn call(&self, a: usize, b: usize, c: usize, d: usize) -> Result<usize> {
        let (pid, uid, gid) = {
            let contexts = context::contexts();
            let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
            let context = context_lock.read();
            (context.id, context.euid, context.egid)
        };

        self.call_inner(Packet {
            id: self.next_id.fetch_add(1, Ordering::SeqCst),
            pid: pid.into(),
            uid: uid,
            gid: gid,
            a: a,
            b: b,
            c: c,
            d: d
        })
    }

    fn call_inner(&self, packet: Packet) -> Result<usize> {
        let id = packet.id;

        let len = self.todo.send(packet);
        context::event::trigger(self.root_id, self.handle_id, EVENT_READ, mem::size_of::<Packet>() * len);

        Error::demux(self.done.receive(&id))
    }

    pub fn capture(&self, buf: &[u8]) -> Result<usize> {
        UserInner::capture_inner(&self.context, buf.as_ptr() as usize, buf.len(), false)
    }

    pub fn capture_mut(&self, buf: &mut [u8]) -> Result<usize> {
        UserInner::capture_inner(&self.context, buf.as_mut_ptr() as usize, buf.len(), true)
    }

    fn capture_inner(context_weak: &Weak<RwLock<Context>>, address: usize, size: usize, writable: bool) -> Result<usize> {
        if size == 0 {
            Ok(0)
        } else {
            let context_lock = context_weak.upgrade().ok_or(Error::new(ESRCH))?;
            let context = context_lock.read();

            let mut grants = context.grants.lock();

            let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_table()) };
            let mut temporary_page = TemporaryPage::new(Page::containing_address(VirtualAddress::new(arch::USER_TMP_GRANT_OFFSET)));

            let from_address = (address/4096) * 4096;
            let offset = address - from_address;
            let full_size = ((offset + size + 4095)/4096) * 4096;
            let mut to_address = arch::USER_GRANT_OFFSET;

            let mut flags = entry::PRESENT | entry::NO_EXECUTE | entry::USER_ACCESSIBLE;
            if writable {
                flags |= entry::WRITABLE;
            }

            for i in 0 .. grants.len() {
                let start = grants[i].start_address().get();
                if to_address + full_size < start {
                    grants.insert(i, Grant::map_inactive(
                        VirtualAddress::new(from_address),
                        VirtualAddress::new(to_address),
                        full_size,
                        flags,
                        &mut new_table,
                        &mut temporary_page
                    ));

                    return Ok(to_address + offset);
                } else {
                    let pages = (grants[i].size() + 4095) / 4096;
                    let end = start + pages * 4096;
                    to_address = end;
                }
            }

            grants.push(Grant::map_inactive(
                VirtualAddress::new(from_address),
                VirtualAddress::new(to_address),
                full_size,
                flags,
                &mut new_table,
                &mut temporary_page
            ));

            Ok(to_address + offset)
        }
    }

    pub fn release(&self, address: usize) -> Result<()> {
        if address == 0 {
            Ok(())
        } else {
            let context_lock = self.context.upgrade().ok_or(Error::new(ESRCH))?;
            let context = context_lock.read();

            let mut grants = context.grants.lock();

            let mut new_table = unsafe { InactivePageTable::from_address(context.arch.get_page_table()) };
            let mut temporary_page = TemporaryPage::new(Page::containing_address(VirtualAddress::new(arch::USER_TMP_GRANT_OFFSET)));

            for i in 0 .. grants.len() {
                let start = grants[i].start_address().get();
                let end = start + grants[i].size();
                if address >= start && address < end {
                    grants.remove(i).unmap_inactive(&mut new_table, &mut temporary_page);

                    return Ok(());
                }
            }

            Err(Error::new(EFAULT))
        }
    }

    pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
        let packet_buf = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut Packet, buf.len()/mem::size_of::<Packet>()) };
        Ok(self.todo.receive_into(packet_buf, self.flags & O_NONBLOCK != O_NONBLOCK) * mem::size_of::<Packet>())
    }

    pub fn write(&self, buf: &[u8]) -> Result<usize> {
        let packet_size = mem::size_of::<Packet>();
        let len = buf.len()/packet_size;
        let mut i = 0;
        while i < len {
            let mut packet = unsafe { *(buf.as_ptr() as *const Packet).offset(i as isize) };
            if packet.id == 0 {
                match packet.a {
                    SYS_FEVENT => context::event::trigger(self.scheme_id.load(Ordering::SeqCst), packet.b, packet.c, packet.d),
                    _ => println!("Unknown scheme -> kernel message {}", packet.a)
                }
            } else {
                if let Some((context_weak, size)) = self.fmap.lock().remove(&packet.id) {
                    if let Ok(address) = Error::demux(packet.a) {
                        packet.a = Error::mux(UserInner::capture_inner(&context_weak, address, size, true));
                    }
                }

                self.done.send(packet.id, packet.a);
            }
            i += 1;
        }

        Ok(i * packet_size)
    }

    pub fn fevent(&self, _flags: usize) -> Result<usize> {
        Ok(self.handle_id)
    }

    pub fn fsync(&self) -> Result<usize> {
        Ok(0)
    }
}

/// UserInner has to be wrapped
pub struct UserScheme {
    inner: Weak<UserInner>
}

impl UserScheme {
    pub fn new(inner: Weak<UserInner>) -> UserScheme {
        UserScheme {
            inner: inner
        }
    }
}

impl Scheme for UserScheme {
    fn open(&self, path: &[u8], flags: usize, _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_OPEN, address, path.len(), flags);
        let _ = inner.release(address);
        result
    }

    fn mkdir(&self, path: &[u8], mode: u16, _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_MKDIR, address, path.len(), mode as usize);
        let _ = inner.release(address);
        result
    }

    fn chmod(&self, path: &[u8], mode: u16, _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_CHMOD, address, path.len(), mode as usize);
        let _ = inner.release(address);
        result
    }

    fn rmdir(&self, path: &[u8], _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_RMDIR, address, path.len(), 0);
        let _ = inner.release(address);
        result
    }

    fn unlink(&self, path: &[u8], _uid: u32, _gid: u32) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(path)?;
        let result = inner.call(SYS_UNLINK, address, path.len(), 0);
        let _ = inner.release(address);
        result
    }

    fn dup(&self, file: usize, buf: &[u8]) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(buf)?;
        let result = inner.call(SYS_DUP, file, address, buf.len());
        let _ = inner.release(address);
        result
    }

    fn read(&self, file: usize, buf: &mut [u8]) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture_mut(buf)?;
        let result = inner.call(SYS_READ, file, address, buf.len());
        let _ = inner.release(address);
        result
    }

    fn write(&self, file: usize, buf: &[u8]) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture(buf)?;
        let result = inner.call(SYS_WRITE, file, address, buf.len());
        let _ = inner.release(address);
        result
    }

    fn seek(&self, file: usize, position: usize, whence: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_LSEEK, file, position, whence)
    }

    fn fcntl(&self, file: usize, cmd: usize, arg: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_FCNTL, file, cmd, arg)
    }

    fn fevent(&self, file: usize, flags: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_FEVENT, file, flags, 0)
    }

    fn fmap(&self, file: usize, offset: usize, size: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;

        let (pid, uid, gid, context_lock) = {
            let contexts = context::contexts();
            let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
            let context = context_lock.read();
            (context.id, context.euid, context.egid, Arc::downgrade(&context_lock))
        };

        let id = inner.next_id.fetch_add(1, Ordering::SeqCst);

        inner.fmap.lock().insert(id, (context_lock, size));

        inner.call_inner(Packet {
            id: id,
            pid: pid.into(),
            uid: uid,
            gid: gid,
            a: SYS_FMAP,
            b: file,
            c: offset,
            d: size
        })
    }

    fn fpath(&self, file: usize, buf: &mut [u8]) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture_mut(buf)?;
        let result = inner.call(SYS_FPATH, file, address, buf.len());
        let _ = inner.release(address);
        result
    }

    fn fstat(&self, file: usize, stat: &mut Stat) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture_mut(stat)?;
        let result = inner.call(SYS_FSTAT, file, address, mem::size_of::<Stat>());
        let _ = inner.release(address);
        result
    }

    fn fstatvfs(&self, file: usize, stat: &mut StatVfs) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        let address = inner.capture_mut(stat)?;
        let result = inner.call(SYS_FSTATVFS, file, address, mem::size_of::<StatVfs>());
        let _ = inner.release(address);
        result
    }

    fn fsync(&self, file: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_FSYNC, file, 0, 0)
    }

    fn ftruncate(&self, file: usize, len: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_FTRUNCATE, file, len, 0)
    }

    fn close(&self, file: usize) -> Result<usize> {
        let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
        inner.call(SYS_CLOSE, file, 0, 0)
    }
}
<|endoftext|>