<commit_before>#![deny(warnings)]
#![feature(const_fn)]

extern crate orbclient;
extern crate orbimage;
extern crate orbfont;

use std::env;
use std::fs::File;
use std::path::Path;
use std::process::Command;

use orbclient::{Color, EventOption, Window};
use orbimage::Image;
use orbfont::Font;

use package::Package;

pub mod package;

const BAR_COLOR: Color = Color::rgb(40, 45, 57);
const BAR_HIGHLIGHT_COLOR: Color = Color::rgb(80, 86, 102);
const TEXT_COLOR: Color = Color::rgb(204, 210, 224);
const TEXT_HIGHLIGHT_COLOR: Color = Color::rgb(235, 241, 255);

//TODO: Implement display size in orbclient
#[cfg(target_os = "redox")]
fn get_display_size() -> (i32, i32) {
    match File::open("display:") {
        Ok(display) => {
            let path = display.path().map(|path| path.into_os_string().into_string().unwrap_or(String::new())).unwrap_or(String::new());
            let res = path.split(":").nth(1).unwrap_or("");
            let width = res.split("/").nth(0).unwrap_or("").parse::<i32>().unwrap_or(0);
            let height = res.split("/").nth(1).unwrap_or("").parse::<i32>().unwrap_or(0);
            (width, height)
        },
        Err(err) => panic!("launcher: failed to get display size: {}", err)
    }
}

#[cfg(not(target_os = "redox"))]
fn get_display_size() -> (i32, i32) {
    panic!("launcher: failed to get display size")
}

fn get_packages() -> Vec<Package> {
    let read_dir = Path::new("/apps/").read_dir().expect("failed to read_dir on /apps/");

    let mut entries = vec![];
    for dir in read_dir {
        let dir = match dir {
            Ok(x) => x,
            Err(_) => continue,
        };
        let file_name = dir.file_name().to_string_lossy().into_owned();
        if dir.file_type().expect("failed to get file_type").is_dir() {
            entries.push(file_name);
        }
    }

    entries.sort();

    let mut packages: Vec<Package> = Vec::new();
    for entry in entries.iter() {
        packages.push(Package::from_path(&format!("/apps/{}", entry)));
    }

    packages
}

fn draw(window: &mut Window, packages: &Vec<Package>, start: &Image, shutdown: &Image, selected: i32){
    let w = window.width();
    let h = window.height();
    window.set(BAR_COLOR);

    let mut x = 0;
    let mut i = 0;

    {
        let y = h as isize - start.height() as isize;

        if i == selected {
            window.rect(x as i32, y as i32,
                              start.width() as u32, start.height() as u32,
                              BAR_HIGHLIGHT_COLOR);
        }

        start.draw(window, x as i32, y as i32);

        x += start.width() as i32;
        i += 1;
    }

    for package in packages.iter() {
        let y = h as isize - package.icon.height() as isize;

        if i == selected {
            window.rect(x as i32, y as i32,
                              package.icon.width() as u32, package.icon.height() as u32,
                              BAR_HIGHLIGHT_COLOR);
        }

        package.icon.draw(window, x as i32, y as i32);

        x += package.icon.width() as i32;
        i += 1;
    }

    {
        x = w as i32 - shutdown.width() as i32;
        let y = h as isize - shutdown.height() as isize;

        if i == selected {
            window.rect(x as i32, y as i32,
                              shutdown.width() as u32, shutdown.height() as u32,
                              BAR_HIGHLIGHT_COLOR);
        }

        shutdown.draw(window, x as i32, y as i32);
    }

    window.sync();
}

fn draw_chooser(window: &mut Window, font: &Font, packages: &Vec<Package>, _mouse_x: i32, mouse_y: i32){
    let w = window.width();

    window.set(BAR_COLOR);

    let mut y = 0;
    for package in packages.iter() {
        let highlight = mouse_y >= y as i32 && mouse_y < y + 32;

        if highlight {
            window.rect(0, y, w, 32, BAR_HIGHLIGHT_COLOR);
        }

        package.icon.draw(window, 0, y);

        let mut c_x = 40;
        for c in package.name.chars() {
            font.render(&c.to_string(), 16.0).draw(window, c_x as i32, y + 8, if highlight { TEXT_HIGHLIGHT_COLOR } else { TEXT_COLOR });
            c_x += 8;
        }

        y += 32;
    }

    window.sync();
}

fn main() {
    let paths = env::args().skip(1);
    if paths.len() > 0 {
        for ref path in paths {
            let mut packages = get_packages();

            packages.retain(|package| -> bool {
                for accept in package.accepts.iter() {
                    if (accept.starts_with('*') && path.ends_with(&accept[1 ..])) ||
                       (accept.ends_with('*') && path.starts_with(&accept[.. accept.len() - 1])) {
                        return true;
                    }
                }
                false
            });

            if packages.len() > 1 {
                let mut window = Window::new(-1, -1, 400, packages.len() as u32 * 32, path).unwrap();
                let font = Font::find(None, None, None).unwrap();

                draw_chooser(&mut window, &font, &packages, -1, -1);
                'choosing: loop {
                    for event in window.events() {
                        match event.to_option() {
                            EventOption::Mouse(mouse_event) => {
                                draw_chooser(&mut window, &font, &packages, mouse_event.x, mouse_event.y);

                                if mouse_event.left_button {
                                    let mut y = 0;
                                    for package in packages.iter() {
                                        if mouse_event.y >= y && mouse_event.y < y + 32 {
                                            if let Err(err) = Command::new(&package.binary).arg(path).spawn() {
                                                println!("{}: Failed to launch: {}", package.binary, err);
                                            }
                                            break 'choosing;
                                        }
                                        y += 32;
                                    }
                                }
                            },
                            EventOption::Quit(_) => break 'choosing,
                            _ => ()
                        }
                    }
                }
            } else if let Some(package) = packages.get(0) {
                if let Err(err) = Command::new(&package.binary).arg(&path).spawn() {
                    println!("launcher: failed to launch '{}': {}", package.binary, err);
                }
            } else {
                println!("launcher: no application found for '{}'", path);
            }
        }
    } else {
        let packages = get_packages();

        let start = Image::from_path("/ui/start.png").unwrap_or(Image::default());

        let shutdown = Image::from_path("/ui/actions/system-shutdown.bmp").unwrap_or(Image::default());

        let (width, height) = get_display_size();
        let mut window = Window::new(0, height - 32, width as u32, 32, "").unwrap();

        let mut selected = -1;

        draw(&mut window, &packages, &start, &shutdown, selected);
        'running: loop {
            for event in window.events() {
                match event.to_option() {
                    EventOption::Mouse(mouse_event) => {
                        let mut now_selected = -1;

                        {
                            let mut x = 0;
                            let mut i = 0;

                            {
                                let y = window.height() as i32 - start.height() as i32;
                                if mouse_event.y >= y && mouse_event.x >= x &&
                                   mouse_event.x < x + start.width() as i32 {
                                       now_selected = i;
                                }
                                x += start.width() as i32;
                                i += 1;
                            }

                            for package in packages.iter() {
                                let y = window.height() as i32 - package.icon.height() as i32;
                                if mouse_event.y >= y && mouse_event.x >= x &&
                                   mouse_event.x < x + package.icon.width() as i32 {
                                    now_selected = i;
                                }
                                x += package.icon.width() as i32;
                                i += 1;
                            }

                            {
                                x = window.width() as i32 - shutdown.width() as i32;
                                let y = window.height() as i32 - shutdown.height() as i32;
                                if mouse_event.y >= y && mouse_event.x >= x &&
                                   mouse_event.x < x + shutdown.width() as i32 {
                                       now_selected = i;
                                }
                            }
                        }

                        if now_selected != selected {
                            selected = now_selected;
                            draw(&mut window, &packages, &start, &shutdown, selected);
                        }

                        if mouse_event.left_button {
                            let mut i = 0;

                            if i == selected {
                                let start_h = packages.len() as u32 * 32;
                                let mut start_window = Window::new(0, height - 32 - start_h as i32, 400, start_h, "Start").unwrap();
                                let font = Font::find(None, None, None).unwrap();

                                draw_chooser(&mut start_window, &font, &packages, -1, -1);
                                'start_choosing: loop {
                                    for event in start_window.events() {
                                        match event.to_option() {
                                            EventOption::Mouse(mouse_event) => {
                                                draw_chooser(&mut start_window, &font, &packages, mouse_event.x, mouse_event.y);

                                                if mouse_event.left_button {
                                                    let mut y = 0;
                                                    for package in packages.iter() {
                                                        if mouse_event.y >= y && mouse_event.y < y + 32 {
                                                            if let Err(err) = Command::new(&package.binary).spawn() {
                                                                println!("{}: Failed to launch: {}", package.binary, err);
                                                            }
                                                            break 'start_choosing;
                                                        }
                                                        y += 32;
                                                    }
                                                }
                                            },
                                            EventOption::Quit(_) => break 'start_choosing,
                                            _ => ()
                                        }
                                    }
                                }
                            }
                            i += 1;

                            for package in packages.iter() {
                                if i == selected {
                                    if let Err(err) = Command::new(&package.binary).spawn() {
                                        println!("{}: Failed to launch: {}", package.binary, err);
                                    }
                                }
                                i += 1;
                            }

                            if i == selected {
                                   File::create("acpi:off").unwrap();
                            }
                        }
                    },
                    EventOption::Quit(_) => break 'running,
                    _ => ()
                }
            }
        }
    }
}
<commit_msg>Hide start menu title<commit_after>#![deny(warnings)]
#![feature(const_fn)]

extern crate orbclient;
extern crate orbimage;
extern crate orbfont;

use std::env;
use std::fs::File;
use std::path::Path;
use std::process::Command;

use orbclient::{Color, EventOption, Window, K_ESC};
use orbimage::Image;
use orbfont::Font;

use package::Package;

pub mod package;

const BAR_COLOR: Color = Color::rgb(40, 45, 57);
const BAR_HIGHLIGHT_COLOR: Color = Color::rgb(80, 86, 102);
const TEXT_COLOR: Color = Color::rgb(204, 210, 224);
const TEXT_HIGHLIGHT_COLOR: Color = Color::rgb(235, 241, 255);

//TODO: Implement display size in orbclient
#[cfg(target_os = "redox")]
fn get_display_size() -> (i32, i32) {
    match File::open("display:") {
        Ok(display) => {
            let path = display.path().map(|path| path.into_os_string().into_string().unwrap_or(String::new())).unwrap_or(String::new());
            let res = path.split(":").nth(1).unwrap_or("");
            let width = res.split("/").nth(0).unwrap_or("").parse::<i32>().unwrap_or(0);
            let height = res.split("/").nth(1).unwrap_or("").parse::<i32>().unwrap_or(0);
            (width, height)
        },
        Err(err) => panic!("launcher: failed to get display size: {}", err)
    }
}

#[cfg(not(target_os = "redox"))]
fn get_display_size() -> (i32, i32) {
    panic!("launcher: failed to get display size")
}

fn get_packages() -> Vec<Package> {
    let read_dir = Path::new("/apps/").read_dir().expect("failed to read_dir on /apps/");

    let mut entries = vec![];
    for dir in read_dir {
        let dir = match dir {
            Ok(x) => x,
            Err(_) => continue,
        };
        let file_name = dir.file_name().to_string_lossy().into_owned();
        if dir.file_type().expect("failed to get file_type").is_dir() {
            entries.push(file_name);
        }
    }

    entries.sort();

    let mut packages: Vec<Package> = Vec::new();
    for entry in entries.iter() {
        packages.push(Package::from_path(&format!("/apps/{}", entry)));
    }

    packages
}

fn draw(window: &mut Window, packages: &Vec<Package>, start: &Image, shutdown: &Image, selected: i32){
    let w = window.width();
    let h = window.height();
    window.set(BAR_COLOR);

    let mut x = 0;
    let mut i = 0;

    {
        let y = h as isize - start.height() as isize;

        if i == selected {
            window.rect(x as i32, y as i32,
                              start.width() as u32, start.height() as u32,
                              BAR_HIGHLIGHT_COLOR);
        }

        start.draw(window, x as i32, y as i32);

        x += start.width() as i32;
        i += 1;
    }

    for package in packages.iter() {
        let y = h as isize - package.icon.height() as isize;

        if i == selected {
            window.rect(x as i32, y as i32,
                              package.icon.width() as u32, package.icon.height() as u32,
                              BAR_HIGHLIGHT_COLOR);
        }

        package.icon.draw(window, x as i32, y as i32);

        x += package.icon.width() as i32;
        i += 1;
    }

    {
        x = w as i32 - shutdown.width() as i32;
        let y = h as isize - shutdown.height() as isize;

        if i == selected {
            window.rect(x as i32, y as i32,
                              shutdown.width() as u32, shutdown.height() as u32,
                              BAR_HIGHLIGHT_COLOR);
        }

        shutdown.draw(window, x as i32, y as i32);
    }

    window.sync();
}

fn draw_chooser(window: &mut Window, font: &Font, packages: &Vec<Package>, _mouse_x: i32, mouse_y: i32){
    let w = window.width();

    window.set(BAR_COLOR);

    let mut y = 0;
    for package in packages.iter() {
        let highlight = mouse_y >= y as i32 && mouse_y < y + 32;

        if highlight {
            window.rect(0, y, w, 32, BAR_HIGHLIGHT_COLOR);
        }

        package.icon.draw(window, 0, y);

        let mut c_x = 40;
        for c in package.name.chars() {
            font.render(&c.to_string(), 16.0).draw(window, c_x as i32, y + 8, if highlight { TEXT_HIGHLIGHT_COLOR } else { TEXT_COLOR });
            c_x += 8;
        }

        y += 32;
    }

    window.sync();
}

fn main() {
    let paths = env::args().skip(1);
    if paths.len() > 0 {
        for ref path in paths {
            let mut packages = get_packages();

            packages.retain(|package| -> bool {
                for accept in package.accepts.iter() {
                    if (accept.starts_with('*') && path.ends_with(&accept[1 ..])) ||
                       (accept.ends_with('*') && path.starts_with(&accept[.. accept.len() - 1])) {
                        return true;
                    }
                }
                false
            });

            if packages.len() > 1 {
                let mut window = Window::new(-1, -1, 400, packages.len() as u32 * 32, path).unwrap();
                let font = Font::find(None, None, None).unwrap();

                draw_chooser(&mut window, &font, &packages, -1, -1);
                'choosing: loop {
                    for event in window.events() {
                        match event.to_option() {
                            EventOption::Mouse(mouse_event) => {
                                draw_chooser(&mut window, &font, &packages, mouse_event.x, mouse_event.y);

                                if mouse_event.left_button {
                                    let mut y = 0;
                                    for package in packages.iter() {
                                        if mouse_event.y >= y && mouse_event.y < y + 32 {
                                            if let Err(err) = Command::new(&package.binary).arg(path).spawn() {
                                                println!("{}: Failed to launch: {}", package.binary, err);
                                            }
                                            break 'choosing;
                                        }
                                        y += 32;
                                    }
                                }
                            },
                            EventOption::Quit(_) => break 'choosing,
                            _ => ()
                        }
                    }
                }
            } else if let Some(package) = packages.get(0) {
                if let Err(err) = Command::new(&package.binary).arg(&path).spawn() {
                    println!("launcher: failed to launch '{}': {}", package.binary, err);
                }
            } else {
                println!("launcher: no application found for '{}'", path);
            }
        }
    } else {
        let packages = get_packages();

        let start = Image::from_path("/ui/start.png").unwrap_or(Image::default());

        let shutdown = Image::from_path("/ui/actions/system-shutdown.bmp").unwrap_or(Image::default());

        let (width, height) = get_display_size();
        let mut window = Window::new(0, height - 32, width as u32, 32, "").unwrap();

        let mut selected = -1;

        draw(&mut window, &packages, &start, &shutdown, selected);
        'running: loop {
            for event in window.events() {
                match event.to_option() {
                    EventOption::Mouse(mouse_event) => {
                        let mut now_selected = -1;

                        {
                            let mut x = 0;
                            let mut i = 0;

                            {
                                let y = window.height() as i32 - start.height() as i32;
                                if mouse_event.y >= y && mouse_event.x >= x &&
                                   mouse_event.x < x + start.width() as i32 {
                                       now_selected = i;
                                }
                                x += start.width() as i32;
                                i += 1;
                            }

                            for package in packages.iter() {
                                let y = window.height() as i32 - package.icon.height() as i32;
                                if mouse_event.y >= y && mouse_event.x >= x &&
                                   mouse_event.x < x + package.icon.width() as i32 {
                                    now_selected = i;
                                }
                                x += package.icon.width() as i32;
                                i += 1;
                            }

                            {
                                x = window.width() as i32 - shutdown.width() as i32;
                                let y = window.height() as i32 - shutdown.height() as i32;
                                if mouse_event.y >= y && mouse_event.x >= x &&
                                   mouse_event.x < x + shutdown.width() as i32 {
                                       now_selected = i;
                                }
                            }
                        }

                        if now_selected != selected {
                            selected = now_selected;
                            draw(&mut window, &packages, &start, &shutdown, selected);
                        }

                        if mouse_event.left_button {
                            let mut i = 0;

                            if i == selected {
                                let start_h = packages.len() as u32 * 32;
                                let mut start_window = Window::new(0, height - 32 - start_h as i32, 400, start_h, "").unwrap();
                                let font = Font::find(None, None, None).unwrap();

                                draw_chooser(&mut start_window, &font, &packages, -1, -1);
                                'start_choosing: loop {
                                    for event in start_window.events() {
                                        match event.to_option() {
                                            EventOption::Mouse(mouse_event) => {
                                                draw_chooser(&mut start_window, &font, &packages, mouse_event.x, mouse_event.y);

                                                if mouse_event.left_button {
                                                    let mut y = 0;
                                                    for package in packages.iter() {
                                                        if mouse_event.y >= y && mouse_event.y < y + 32 {
                                                            if let Err(err) = Command::new(&package.binary).spawn() {
                                                                println!("{}: Failed to launch: {}", package.binary, err);
                                                            }
                                                            break 'start_choosing;
                                                        }
                                                        y += 32;
                                                    }
                                                }
                                            },
                                            EventOption::Key(key_event) => {
                                                match key_event.scancode {
                                                    K_ESC => break 'start_choosing,
                                                    _ => ()
                                                }
                                            },
                                            EventOption::Quit(_) => break 'start_choosing,
                                            _ => ()
                                        }
                                    }
                                }
                            }
                            i += 1;

                            for package in packages.iter() {
                                if i == selected {
                                    if let Err(err) = Command::new(&package.binary).spawn() {
                                        println!("{}: Failed to launch: {}", package.binary, err);
                                    }
                                }
                                i += 1;
                            }

                            if i == selected {
                                   File::create("acpi:off").unwrap();
                            }
                        }
                    },
                    EventOption::Quit(_) => break 'running,
                    _ => ()
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>//! Web interface of cratesfyi

mod rustdoc;
mod releases;
mod page;
mod crate_details;
mod source;
mod pool;
mod file;
mod builds;
mod error;

use std::env;
use std::error::Error;
use std::time::Duration;
use std::path::PathBuf;
use iron::prelude::*;
use iron::Handler;
use router::{Router, NoRoute};
use staticfile::Static;
use handlebars_iron::{HandlebarsEngine, DirectorySource};
use time;
use postgres::Connection;
use semver::{Version, VersionReq};
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;

/// Duration of static files for staticfile and DatabaseFileHandler (in seconds)
const STATIC_FILE_CACHE_DURATION: u64 = 60 * 60 * 24 * 30 * 12;   // 12 months

struct CratesfyiHandler {
    router_handler: Box<Handler>,
    database_file_handler: Box<Handler>,
    static_handler: Box<Handler>,
}

impl CratesfyiHandler {
    fn chain<H: Handler>(base: H) -> Chain {
        // TODO: Use DocBuilderOptions for paths
        let mut hbse = HandlebarsEngine::new();
        hbse.add(Box::new(DirectorySource::new("./templates", ".hbs")));

        // load templates
        if let Err(e) = hbse.reload() {
            panic!("Failed to load handlebar templates: {}", e.description());
        }

        let mut chain = Chain::new(base);
        chain.link_before(pool::Pool::new());
        chain.link_after(hbse);
        chain
    }

    pub fn new() -> CratesfyiHandler {
        let mut router = Router::new();
        router.get("/", releases::home_page);
        router.get("/about", |_: &mut Request| {
            page::Page::new(false).title("About Docs.rs").to_resp("about")
        });
        router.get("/releases", releases::releases_handler);
        router.get("/releases/recent/:page", releases::releases_handler);
        router.get("/releases/stars", releases::stars_handler);
        router.get("/releases/stars/:page", releases::stars_handler);
        router.get("/releases/:author", releases::author_handler);
        router.get("/releases/:author/:page", releases::author_handler);
        router.get("/releases/activity", releases::activity_handler);
        router.get("/releases/search", releases::search_handler);
        router.get("/crate/:name", crate_details::crate_details_handler);
        router.get("/crate/:name/", crate_details::crate_details_handler);
        router.get("/crate/:name/:version", crate_details::crate_details_handler);
        router.get("/crate/:name/:version/", crate_details::crate_details_handler);
        router.get("/crate/:name/:version/builds", builds::build_list_handler);
        router.get("/crate/:name/:version/builds/:id", builds::build_list_handler);
        router.get("/crate/:name/:version/source/", source::source_browser_handler);
        router.get("/crate/:name/:version/source/*", source::source_browser_handler);
        router.get("/:crate", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/badge.svg", rustdoc::badge_handler);
        router.get("/:crate/:version", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/search-index.js", rustdoc::rustdoc_html_server_handler);
        router.get("/:crate/:version/:target", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/:target/", rustdoc::rustdoc_html_server_handler);
        router.get("/:crate/:version/:target/*.html", rustdoc::rustdoc_html_server_handler);

        let router_chain = Self::chain(router);
        let prefix = PathBuf::from(env::var("CRATESFYI_PREFIX").unwrap()).join("public_html");
        let static_handler = Static::new(prefix)
            .cache(Duration::from_secs(STATIC_FILE_CACHE_DURATION));

        CratesfyiHandler {
            router_handler: Box::new(router_chain),
            database_file_handler: Box::new(file::DatabaseFileHandler),
            static_handler: Box::new(static_handler),
        }
    }
}

impl Handler for CratesfyiHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        // try router first then db/static file handler
        // return 404 if none of them return Ok
        self.router_handler
            .handle(req)
            .or_else(|e| {
                // if router fails try to serve files from database first
                self.database_file_handler.handle(req).or(Err(e))
            })
            .or_else(|e| {
                // and then try static handler. if all of them fails, return 404
                self.static_handler.handle(req).or(Err(e))
            })
            .or_else(|e| {
                debug!("{}", e.description());
                let err = if let Some(err) = e.error.downcast::<error::Nope>() {
                    *err
                } else if e.error.downcast::<NoRoute>().is_some() {
                    error::Nope::ResourceNotFound
                } else {
                    panic!("all cratesfyi errors should be of type Nope");
                };
                Self::chain(err).handle(req)
            })
    }
}

fn match_version(conn: &Connection, name: &str, version: Option<&str>) -> Option<String> {

    // version is an Option<&str> from router::Router::get
    // need to decode first
    use url::percent_encoding::percent_decode;
    let req_version = version.and_then(|v| {
        match percent_decode(v.as_bytes()).decode_utf8() {
            Ok(p) => Some(p.into_owned()),
            Err(_) => None,
        }
    }).unwrap_or("*".to_string());

    let versions = {
        let mut versions = Vec::new();
        let rows = conn.query("SELECT versions FROM crates WHERE name = $1", &[&name]).unwrap();
        if rows.len() == 0 {
            return None;
        }
        let versions_json: Json = rows.get(0).get(0);
        for version in versions_json.as_array().unwrap() {
            let version: String = version.as_string().unwrap().to_owned();
            versions.push(version);
        }

        versions
    };

    // first check for exact match
    // we can't expect users to use semver in query
    for version in &versions {
        if version == &req_version {
            return Some(version.clone())
        }
    }

    // Now try to match with semver
    let req_sem_ver = match VersionReq::parse(&req_version) {
        Ok(v) => v,
        Err(_) => return None,
    };;

    // we need to sort versions first
    let versions_sem = {
        let mut versions_sem: Vec<Version> = Vec::new();

        for version in &versions {
            // in theory a crate must always have a semver compatible version
            // but check result just in case
            let version = match Version::parse(&version) {
                Ok(v) => v,
                Err(_) => return None,
            };
            versions_sem.push(version);
        }

        versions_sem.sort();
        versions_sem.reverse();
        versions_sem
    };

    for version in &versions_sem {
        if req_sem_ver.matches(&version) {
            return Some(format!("{}", version))
        }
    }

    None
}

/// Wrapper around the pulldown-cmark parser and renderer to render markdown
fn render_markdown(text: &str) -> String {
    // I got this from mdBook::src::utils
    use pulldown_cmark::{Parser, html, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
    let mut s = String::with_capacity(text.len() * 3 / 2);

    let mut opts = Options::empty();
    opts.insert(OPTION_ENABLE_TABLES);
    opts.insert(OPTION_ENABLE_FOOTNOTES);

    let p = Parser::new_ext(&text, opts);
    html::push_html(&mut s, p);
    s
}

/// Starts cratesfyi web server
pub fn start_web_server(sock_addr: Option<&str>) {
    let cratesfyi = CratesfyiHandler::new();
    Iron::new(cratesfyi).http(sock_addr.unwrap_or("localhost:3000")).unwrap();
}

/// Converts Timespec to nice readable relative time string
fn duration_to_str(ts: time::Timespec) -> String {

    let tm = time::at(ts);
    let delta = time::now() - tm;

    if delta.num_days() > 5 {
        format!("{}", tm.strftime("%b %d, %Y").unwrap())
    } else if delta.num_days() > 1 {
        format!("{} days ago", delta.num_days())
    } else if delta.num_days() == 1 {
        "one day ago".to_string()
    } else if delta.num_hours() > 1 {
        format!("{} hours ago", delta.num_hours())
    } else if delta.num_hours() == 1 {
        "an hour ago".to_string()
    } else if delta.num_minutes() > 1 {
        format!("{} minutes ago", delta.num_minutes())
    } else if delta.num_minutes() == 1 {
        "one minute ago".to_string()
    } else if delta.num_seconds() > 0 {
        format!("{} seconds ago", delta.num_seconds())
    } else {
        "just now".to_string()
    }

}

/// MetaData used in header
#[derive(Debug)]
pub struct MetaData {
    pub name: String,
    pub version: String,
    pub description: Option<String>,
    pub target_name: Option<String>,
    pub rustdoc_status: bool,
}

impl MetaData {
    pub fn from_crate(conn: &Connection, name: &str, version: &str) -> Option<MetaData> {
        for row in &conn.query("SELECT crates.name,
                                       releases.version,
                                       releases.description,
                                       releases.target_name,
                                       releases.rustdoc_status
                                FROM releases
                                INNER JOIN crates ON crates.id = releases.crate_id
                                WHERE crates.name = $1 AND releases.version = $2",
                               &[&name, &version]).unwrap() {

            return Some(MetaData {
                name: row.get(0),
                version: row.get(1),
                description: row.get(2),
                target_name: row.get(3),
                rustdoc_status: row.get(4),
            });
        }

        None
    }
}

impl ToJson for MetaData {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("name".to_owned(), self.name.to_json());
        m.insert("version".to_owned(), self.version.to_json());
        m.insert("description".to_owned(), self.description.to_json());
        m.insert("target_name".to_owned(), self.target_name.to_json());
        m.insert("rustdoc_status".to_owned(), self.rustdoc_status.to_json());
        m.to_json()
    }
}

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

    #[test]
    #[ignore]
    fn test_start_web_server() {
        // FIXME: This test is doing nothing
        let _ = env_logger::init();
        start_web_server(None);
    }
}
<commit_msg>Add trailing slashed route to redirector<commit_after>//! Web interface of cratesfyi

mod rustdoc;
mod releases;
mod page;
mod crate_details;
mod source;
mod pool;
mod file;
mod builds;
mod error;

use std::env;
use std::error::Error;
use std::time::Duration;
use std::path::PathBuf;
use iron::prelude::*;
use iron::Handler;
use router::{Router, NoRoute};
use staticfile::Static;
use handlebars_iron::{HandlebarsEngine, DirectorySource};
use time;
use postgres::Connection;
use semver::{Version, VersionReq};
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;

/// Duration of static files for staticfile and DatabaseFileHandler (in seconds)
const STATIC_FILE_CACHE_DURATION: u64 = 60 * 60 * 24 * 30 * 12;   // 12 months

struct CratesfyiHandler {
    router_handler: Box<Handler>,
    database_file_handler: Box<Handler>,
    static_handler: Box<Handler>,
}

impl CratesfyiHandler {
    fn chain<H: Handler>(base: H) -> Chain {
        // TODO: Use DocBuilderOptions for paths
        let mut hbse = HandlebarsEngine::new();
        hbse.add(Box::new(DirectorySource::new("./templates", ".hbs")));

        // load templates
        if let Err(e) = hbse.reload() {
            panic!("Failed to load handlebar templates: {}", e.description());
        }

        let mut chain = Chain::new(base);
        chain.link_before(pool::Pool::new());
        chain.link_after(hbse);
        chain
    }

    pub fn new() -> CratesfyiHandler {
        let mut router = Router::new();
        router.get("/", releases::home_page);
        router.get("/about", |_: &mut Request| {
            page::Page::new(false).title("About Docs.rs").to_resp("about")
        });
        router.get("/releases", releases::releases_handler);
        router.get("/releases/recent/:page", releases::releases_handler);
        router.get("/releases/stars", releases::stars_handler);
        router.get("/releases/stars/:page", releases::stars_handler);
        router.get("/releases/:author", releases::author_handler);
        router.get("/releases/:author/:page", releases::author_handler);
        router.get("/releases/activity", releases::activity_handler);
        router.get("/releases/search", releases::search_handler);
        router.get("/crate/:name", crate_details::crate_details_handler);
        router.get("/crate/:name/", crate_details::crate_details_handler);
        router.get("/crate/:name/:version", crate_details::crate_details_handler);
        router.get("/crate/:name/:version/", crate_details::crate_details_handler);
        router.get("/crate/:name/:version/builds", builds::build_list_handler);
        router.get("/crate/:name/:version/builds/:id", builds::build_list_handler);
        router.get("/crate/:name/:version/source/", source::source_browser_handler);
        router.get("/crate/:name/:version/source/*", source::source_browser_handler);
        router.get("/:crate", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/badge.svg", rustdoc::badge_handler);
        router.get("/:crate/:version", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/search-index.js", rustdoc::rustdoc_html_server_handler);
        router.get("/:crate/:version/:target", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/:target/", rustdoc::rustdoc_html_server_handler);
        router.get("/:crate/:version/:target/*.html", rustdoc::rustdoc_html_server_handler);

        let router_chain = Self::chain(router);
        let prefix = PathBuf::from(env::var("CRATESFYI_PREFIX").unwrap()).join("public_html");
        let static_handler = Static::new(prefix)
            .cache(Duration::from_secs(STATIC_FILE_CACHE_DURATION));

        CratesfyiHandler {
            router_handler: Box::new(router_chain),
            database_file_handler: Box::new(file::DatabaseFileHandler),
            static_handler: Box::new(static_handler),
        }
    }
}

impl Handler for CratesfyiHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        // try router first then db/static file handler
        // return 404 if none of them return Ok
        self.router_handler
            .handle(req)
            .or_else(|e| {
                // if router fails try to serve files from database first
                self.database_file_handler.handle(req).or(Err(e))
            })
            .or_else(|e| {
                // and then try static handler. if all of them fails, return 404
                self.static_handler.handle(req).or(Err(e))
            })
            .or_else(|e| {
                debug!("{}", e.description());
                let err = if let Some(err) = e.error.downcast::<error::Nope>() {
                    *err
                } else if e.error.downcast::<NoRoute>().is_some() {
                    error::Nope::ResourceNotFound
                } else {
                    panic!("all cratesfyi errors should be of type Nope");
                };
                Self::chain(err).handle(req)
            })
    }
}

fn match_version(conn: &Connection, name: &str, version: Option<&str>) -> Option<String> {

    // version is an Option<&str> from router::Router::get
    // need to decode first
    use url::percent_encoding::percent_decode;
    let req_version = version.and_then(|v| {
        match percent_decode(v.as_bytes()).decode_utf8() {
            Ok(p) => Some(p.into_owned()),
            Err(_) => None,
        }
    }).unwrap_or("*".to_string());

    let versions = {
        let mut versions = Vec::new();
        let rows = conn.query("SELECT versions FROM crates WHERE name = $1", &[&name]).unwrap();
        if rows.len() == 0 {
            return None;
        }
        let versions_json: Json = rows.get(0).get(0);
        for version in versions_json.as_array().unwrap() {
            let version: String = version.as_string().unwrap().to_owned();
            versions.push(version);
        }

        versions
    };

    // first check for exact match
    // we can't expect users to use semver in query
    for version in &versions {
        if version == &req_version {
            return Some(version.clone())
        }
    }

    // Now try to match with semver
    let req_sem_ver = match VersionReq::parse(&req_version) {
        Ok(v) => v,
        Err(_) => return None,
    };;

    // we need to sort versions first
    let versions_sem = {
        let mut versions_sem: Vec<Version> = Vec::new();

        for version in &versions {
            // in theory a crate must always have a semver compatible version
            // but check result just in case
            let version = match Version::parse(&version) {
                Ok(v) => v,
                Err(_) => return None,
            };
            versions_sem.push(version);
        }

        versions_sem.sort();
        versions_sem.reverse();
        versions_sem
    };

    for version in &versions_sem {
        if req_sem_ver.matches(&version) {
            return Some(format!("{}", version))
        }
    }

    None
}

/// Wrapper around the pulldown-cmark parser and renderer to render markdown
fn render_markdown(text: &str) -> String {
    // I got this from mdBook::src::utils
    use pulldown_cmark::{Parser, html, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
    let mut s = String::with_capacity(text.len() * 3 / 2);

    let mut opts = Options::empty();
    opts.insert(OPTION_ENABLE_TABLES);
    opts.insert(OPTION_ENABLE_FOOTNOTES);

    let p = Parser::new_ext(&text, opts);
    html::push_html(&mut s, p);
    s
}

/// Starts cratesfyi web server
pub fn start_web_server(sock_addr: Option<&str>) {
    let cratesfyi = CratesfyiHandler::new();
    Iron::new(cratesfyi).http(sock_addr.unwrap_or("localhost:3000")).unwrap();
}

/// Converts Timespec to nice readable relative time string
fn duration_to_str(ts: time::Timespec) -> String {

    let tm = time::at(ts);
    let delta = time::now() - tm;

    if delta.num_days() > 5 {
        format!("{}", tm.strftime("%b %d, %Y").unwrap())
    } else if delta.num_days() > 1 {
        format!("{} days ago", delta.num_days())
    } else if delta.num_days() == 1 {
        "one day ago".to_string()
    } else if delta.num_hours() > 1 {
        format!("{} hours ago", delta.num_hours())
    } else if delta.num_hours() == 1 {
        "an hour ago".to_string()
    } else if delta.num_minutes() > 1 {
        format!("{} minutes ago", delta.num_minutes())
    } else if delta.num_minutes() == 1 {
        "one minute ago".to_string()
    } else if delta.num_seconds() > 0 {
        format!("{} seconds ago", delta.num_seconds())
    } else {
        "just now".to_string()
    }

}

/// MetaData used in header
#[derive(Debug)]
pub struct MetaData {
    pub name: String,
    pub version: String,
    pub description: Option<String>,
    pub target_name: Option<String>,
    pub rustdoc_status: bool,
}

impl MetaData {
    pub fn from_crate(conn: &Connection, name: &str, version: &str) -> Option<MetaData> {
        for row in &conn.query("SELECT crates.name,
                                       releases.version,
                                       releases.description,
                                       releases.target_name,
                                       releases.rustdoc_status
                                FROM releases
                                INNER JOIN crates ON crates.id = releases.crate_id
                                WHERE crates.name = $1 AND releases.version = $2",
                               &[&name, &version]).unwrap() {

            return Some(MetaData {
                name: row.get(0),
                version: row.get(1),
                description: row.get(2),
                target_name: row.get(3),
                rustdoc_status: row.get(4),
            });
        }

        None
    }
}

impl ToJson for MetaData {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("name".to_owned(), self.name.to_json());
        m.insert("version".to_owned(), self.version.to_json());
        m.insert("description".to_owned(), self.description.to_json());
        m.insert("target_name".to_owned(), self.target_name.to_json());
        m.insert("rustdoc_status".to_owned(), self.rustdoc_status.to_json());
        m.to_json()
    }
}

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

    #[test]
    #[ignore]
    fn test_start_web_server() {
        // FIXME: This test is doing nothing
        let _ = env_logger::init();
        start_web_server(None);
    }
}
<|endoftext|>


<commit_before><commit_msg>Fixed not working example from NOC<commit_after><|endoftext|>


<commit_before>use libc::*;
use std::mem;
use std::collections::VecDeque;
// I tried
// It's a bit hard to write this without a Windows pc on hand...
// But this should work on Windows Vista, 7, and 8
#[link(name = "XINPUT9_1_0")]
extern "stdcall" {
	fn XInputGetCapabilities(index: u32, flags: u32, capabilities: *mut Capabilities) -> i32;
	fn XInputGetState(index: u32, state: *mut State) -> i32;
}

#[repr(C)]
struct State {
	packet: i32,
	gamepad: Gamepad
}

#[repr(C)]
struct Capabilities {
	_type: u8,
	sub_type: u8,
	flags: u32,
	gamepad: Gamepad,
	vibration: Vibration
}

bitflags!(
	flags Buttons: u32 {
		const DPAD_UP = 0x0001,
		const DPAD_DOWN = 0x0002,
		const DPAD_LEFT = 0x0004,
		const DPAD_RIGHT = 0x0008,
		const START = 0x0010,
		const BACK = 0x0020,
		const LEFT_THUMB = 0x0040,
		const RIGHT_THUMB = 0x0080,
		const LEFT_SHOULDER = 0x0100,
		const RIGHT_SHOULDER = 0x0200,
		const A = 0x1000,
		const B = 0x2000,
		const X = 0x4000,
		const Y = 0x8000,
	}
);
#[repr(C)]
#[derive(Copy)]
struct Gamepad {
	buttons: Buttons,
	left_trigger: u8,
	right_trigger: u8,
	thumb_lx: i16,
	thumb_ly: i16,
	thumb_rx: i16,
	thumb_ry: i16
}
#[repr(C)]
struct Vibration {
	left_motor_speed: i32,
	right_motor_speed: i32
}

/// Scan for joysticks
pub fn scan() -> Vec<Joystick> {
	(0..4).filter_map(|i| Joystick::new(i).ok()).collect()
}

pub struct NativeJoystick {
	index: u8,
	last: Gamepad,
	last_packet: i32,
	events: VecDeque<::Event>
}
impl ::Joystick for NativeJoystick {
	type WithState = NativeJoystick;
	fn new(index: u8) -> Result<NativeJoystick, &'static str> {
		unsafe {
			let mut caps: Capabilities = mem::uninitialized();
			match XInputGetCapabilities(index as u32, 0, &mut caps) {
				0 =>
					Ok(NativeJoystick {
						index: index,
						last: caps.gamepad,
						last_packet: 0,
						events: VecDeque::with_capacity(10)
					}),
				0x48F => Err("Device not connected"),
				_ => Err("Unknown Error")
			}
		}
	}
	fn poll(&mut self) -> Option<::Event> {
		use ::StatefulJoystick;
		self.update();
		self.events.pop_back()
	}
	fn is_connected(&self) -> bool {
		true
	}
	fn get_id(&self) -> String {
		String::from_str("XInput Device")
	}
	fn get_index(&self) -> u8 {
		self.index
	}
	fn get_num_buttons(&self) -> u8 {
		4
	}
	fn get_num_axes(&self) -> u8 {
		4
	}
	fn with_state(self) -> NativeJoystick {
		self
	}
}
macro_rules! event{
	(button $this:expr, $now:expr, $last:expr, $btn:expr, $id:expr) => (
		{
			let now_btn = $now.buttons.contains($btn);
			let last_btn = $last.buttons.contains($btn);
			if now_btn && !last_btn {
				$this.events.push_back(::Event::ButtonPressed($id))
			} else if !now_btn && last_btn {
				$this.events.push_back(::Event::ButtonReleased($id))
			}
		}
	);
	(axis $this:expr, $now:expr, $last:expr, $field:ident, $id:expr) => (
		if $now.$field != $last.$field {
			$this.events.push_back(::Event::JoystickMoved($id, $now.$field))
		}
	);
	(axes $this:expr, $now:expr, $last:expr, $($field:ident => $id:expr),+) => ({
		$(event!(axis $this, $now, $last, $field, $id);)+
	});
	(buttons $this:expr, $now:expr, $last:expr, $($btn:ident => $id:expr),+) => ({
		$(event!(button $this, $now, $last, $btn, $id);)+
	});
}
impl ::StatefulJoystick for NativeJoystick {
	fn get_axis(&self, index: u8) -> Option<i16> {
		match index {
			0 => Some(self.last.thumb_lx),
			1 => Some(self.last.thumb_ly),
			2 => Some(self.last.thumb_rx),
			3 => Some(self.last.thumb_ry),
			_ => None
		}
	}
	fn get_button(&self, index: u8) -> Option<bool> {
		let bits = match index {
		0 => Some(Y),
			1 => Some(A),
			2 => Some(B),
			3 => Some(X),
			4 => Some(LEFT_SHOULDER),
			5 => Some(RIGHT_SHOULDER),
			8 => Some(BACK),
			9 => Some(START),
			10 => Some(LEFT_TRIGGER),
			11 => Some(RIGHT_TRIGGER),
			_ => None
		};
		bits.map(|v| self.last.buttons.contains(v))
	}
	fn update(&mut self) {
		let state = unsafe {
			let mut state: State = mem::uninitialized();
			XInputGetState(self.index as u32, &mut state);
			state
		};
		let now = state.gamepad;
		if state.packet != self.last_packet {
			let last = self.last;
			event!{axes self, now, last,
				thumb_lx => 0,
				thumb_ly => 1,
				thumb_rx => 2,
				thumb_ry => 3
			}
			event!{buttons self, now, last,
			Y => 0,
				A => 1,
				B => 2,
				X => 3,
				LEFT_SHOULDER => 4,
				RIGHT_SHOULDER => 5,
				BACK => 8,
				START => 9,
				LEFT_TRIGGER => 10,
				RIGHT_TRIGGER => 11
			}
		}
		self.last_packet = state.packet;
		self.last = now;
	}
}
<commit_msg>Use correct name to link to XInput<commit_after>use libc::*;
use std::mem;
use std::collections::VecDeque;
// I tried
// It's a bit hard to write this without a Windows pc on hand...
// But this should work on Windows Vista, 7, and 8
#[link(name = "Xinput9_1_0")]
extern "stdcall" {
	fn XInputGetCapabilities(index: u32, flags: u32, capabilities: *mut Capabilities) -> i32;
	fn XInputGetState(index: u32, state: *mut State) -> i32;
}

#[repr(C)]
struct State {
	packet: i32,
	gamepad: Gamepad
}

#[repr(C)]
struct Capabilities {
	_type: u8,
	sub_type: u8,
	flags: u32,
	gamepad: Gamepad,
	vibration: Vibration
}

bitflags!(
	flags Buttons: u32 {
		const DPAD_UP = 0x0001,
		const DPAD_DOWN = 0x0002,
		const DPAD_LEFT = 0x0004,
		const DPAD_RIGHT = 0x0008,
		const START = 0x0010,
		const BACK = 0x0020,
		const LEFT_THUMB = 0x0040,
		const RIGHT_THUMB = 0x0080,
		const LEFT_SHOULDER = 0x0100,
		const RIGHT_SHOULDER = 0x0200,
		const A = 0x1000,
		const B = 0x2000,
		const X = 0x4000,
		const Y = 0x8000,
	}
);
#[repr(C)]
#[derive(Copy)]
struct Gamepad {
	buttons: Buttons,
	left_trigger: u8,
	right_trigger: u8,
	thumb_lx: i16,
	thumb_ly: i16,
	thumb_rx: i16,
	thumb_ry: i16
}
#[repr(C)]
struct Vibration {
	left_motor_speed: i32,
	right_motor_speed: i32
}

/// Scan for joysticks
pub fn scan() -> Vec<Joystick> {
	(0..4).filter_map(|i| Joystick::new(i).ok()).collect()
}

pub struct NativeJoystick {
	index: u8,
	last: Gamepad,
	last_packet: i32,
	events: VecDeque<::Event>
}
impl ::Joystick for NativeJoystick {
	type WithState = NativeJoystick;
	fn new(index: u8) -> Result<NativeJoystick, &'static str> {
		unsafe {
			let mut caps: Capabilities = mem::uninitialized();
			match XInputGetCapabilities(index as u32, 0, &mut caps) {
				0 =>
					Ok(NativeJoystick {
						index: index,
						last: caps.gamepad,
						last_packet: 0,
						events: VecDeque::with_capacity(10)
					}),
				0x48F => Err("Device not connected"),
				_ => Err("Unknown Error")
			}
		}
	}
	fn poll(&mut self) -> Option<::Event> {
		use ::StatefulJoystick;
		self.update();
		self.events.pop_back()
	}
	fn is_connected(&self) -> bool {
		true
	}
	fn get_id(&self) -> String {
		String::from_str("XInput Device")
	}
	fn get_index(&self) -> u8 {
		self.index
	}
	fn get_num_buttons(&self) -> u8 {
		4
	}
	fn get_num_axes(&self) -> u8 {
		4
	}
	fn with_state(self) -> NativeJoystick {
		self
	}
}
macro_rules! event{
	(button $this:expr, $now:expr, $last:expr, $btn:expr, $id:expr) => (
		{
			let now_btn = $now.buttons.contains($btn);
			let last_btn = $last.buttons.contains($btn);
			if now_btn && !last_btn {
				$this.events.push_back(::Event::ButtonPressed($id))
			} else if !now_btn && last_btn {
				$this.events.push_back(::Event::ButtonReleased($id))
			}
		}
	);
	(axis $this:expr, $now:expr, $last:expr, $field:ident, $id:expr) => (
		if $now.$field != $last.$field {
			$this.events.push_back(::Event::JoystickMoved($id, $now.$field))
		}
	);
	(axes $this:expr, $now:expr, $last:expr, $($field:ident => $id:expr),+) => ({
		$(event!(axis $this, $now, $last, $field, $id);)+
	});
	(buttons $this:expr, $now:expr, $last:expr, $($btn:ident => $id:expr),+) => ({
		$(event!(button $this, $now, $last, $btn, $id);)+
	});
}
impl ::StatefulJoystick for NativeJoystick {
	fn get_axis(&self, index: u8) -> Option<i16> {
		match index {
			0 => Some(self.last.thumb_lx),
			1 => Some(self.last.thumb_ly),
			2 => Some(self.last.thumb_rx),
			3 => Some(self.last.thumb_ry),
			_ => None
		}
	}
	fn get_button(&self, index: u8) -> Option<bool> {
		let bits = match index {
		0 => Some(Y),
			1 => Some(A),
			2 => Some(B),
			3 => Some(X),
			4 => Some(LEFT_SHOULDER),
			5 => Some(RIGHT_SHOULDER),
			8 => Some(BACK),
			9 => Some(START),
			10 => Some(LEFT_TRIGGER),
			11 => Some(RIGHT_TRIGGER),
			_ => None
		};
		bits.map(|v| self.last.buttons.contains(v))
	}
	fn update(&mut self) {
		let state = unsafe {
			let mut state: State = mem::uninitialized();
			XInputGetState(self.index as u32, &mut state);
			state
		};
		let now = state.gamepad;
		if state.packet != self.last_packet {
			let last = self.last;
			event!{axes self, now, last,
				thumb_lx => 0,
				thumb_ly => 1,
				thumb_rx => 2,
				thumb_ry => 3
			}
			event!{buttons self, now, last,
			Y => 0,
				A => 1,
				B => 2,
				X => 3,
				LEFT_SHOULDER => 4,
				RIGHT_SHOULDER => 5,
				BACK => 8,
				START => 9,
				LEFT_TRIGGER => 10,
				RIGHT_TRIGGER => 11
			}
		}
		self.last_packet = state.packet;
		self.last = now;
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

mod fetchable_query_generator;
mod node_query_generator;
mod query_query_generator;
mod utils;
mod viewer_query_generator;

use crate::connections::{extract_connection_metadata_from_directive, ConnectionConstants};
use crate::root_variables::{InferVariablesVisitor, VariableMap};

use common::{NamedItem, WithLocation};
use errors::validate_map;
use fetchable_query_generator::FETCHABLE_QUERY_GENERATOR;
use fnv::{FnvHashMap, FnvHashSet};
use graphql_ir::{
    FragmentDefinition, OperationDefinition, Program, ValidationError, ValidationMessage,
    ValidationResult,
};
use graphql_text_printer::print_value;
use interner::StringKey;
use node_query_generator::NODE_QUERY_GENERATOR;
use query_query_generator::QUERY_QUERY_GENERATOR;
use schema::Schema;
use std::fmt::Write;
use std::sync::Arc;
use utils::*;
pub use utils::{
    extract_refetch_metadata_from_directive, RefetchableDerivedFromMetadata, CONSTANTS,
};
use viewer_query_generator::VIEWER_QUERY_GENERATOR;

/// This transform synthesizes "refetch" queries for fragments that
/// are trivially refetchable. This is comprised of three main stages:
///
/// 1. Validating that fragments marked with @refetchable qualify for
///    refetch query generation; mainly this means that the fragment
///    type is able to be refetched in some canonical way.
/// 2. Determining the variable definitions to use for each generated
///    query. GraphQL does not have a notion of fragment-local variables
///    at all, and although Relay adds this concept developers are still
///    allowed to reference global variables. This necessitates a
///    visiting all reachable fragments for each @refetchable fragment,
///    and finding the union of all global variables expceted to be defined.
/// 3. Building the refetch queries, a straightforward copying transform from
///    Fragment to Root IR nodes.
pub fn transform_refetchable_fragment(
    program: &Program,
    base_fragment_names: &'_ FnvHashSet<StringKey>,
    for_typegen: bool,
) -> ValidationResult<Program> {
    let mut next_program = Program::new(Arc::clone(&program.schema));

    let mut transformer = RefetchableFragment {
        connection_constants: Default::default(),
        existing_refetch_operations: Default::default(),
        for_typegen,
        program,
        visitor: InferVariablesVisitor::new(program),
    };

    for operation in program.operations() {
        next_program.insert_operation(Arc::clone(operation));
    }

    validate_map(program.fragments(), |fragment| {
        if base_fragment_names.contains(&fragment.name.item) {
            next_program.insert_fragment(Arc::clone(fragment));
        } else {
            let operation_result = transformer.transform_refetch_fragment(fragment)?;
            if let Some(operation_result) = operation_result {
                next_program.insert_fragment(operation_result.fragment);
                next_program.insert_operation(operation_result.operation);
            } else {
                next_program.insert_fragment(Arc::clone(fragment));
            }
        }
        Ok(())
    })?;

    Ok(next_program)
}

type ExistingRefetchOperations = FnvHashMap<StringKey, WithLocation<StringKey>>;

struct RefetchableFragment<'program> {
    connection_constants: ConnectionConstants,
    existing_refetch_operations: ExistingRefetchOperations,
    for_typegen: bool,
    program: &'program Program,
    visitor: InferVariablesVisitor<'program>,
}

impl RefetchableFragment<'_> {
    fn transform_refetch_fragment(
        &mut self,
        fragment: &Arc<FragmentDefinition>,
    ) -> ValidationResult<Option<RefetchRoot>> {
        let refetch_name = self.get_refetch_query_name(fragment)?;
        if let Some(refetch_name) = refetch_name {
            let variables_map = self.visitor.infer_fragment_variables(fragment);
            for generator in GENERATORS.iter() {
                if let Some(refetch_root) = (generator.build_refetch_operation)(
                    &self.program.schema,
                    fragment,
                    refetch_name,
                    &variables_map,
                )? {
                    if !self.for_typegen {
                        self.validate_connection_metadata(refetch_root.fragment.as_ref())?;
                    }
                    return Ok(Some(refetch_root));
                }
            }
            let mut descriptions = String::new();
            for generator in GENERATORS.iter() {
                writeln!(descriptions, " - {}", generator.description).unwrap();
            }
            descriptions.pop();
            Err(vec![ValidationError::new(
                ValidationMessage::UnsupportedRefetchableFragment {
                    fragment_name: fragment.name.item,
                    descriptions,
                },
                vec![fragment.name.location],
            )])
        } else {
            Ok(None)
        }
    }

    fn get_refetch_query_name(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> ValidationResult<Option<StringKey>> {
        if let Some(directive) = fragment.directives.named(CONSTANTS.refetchable_name) {
            if let Some(query_name_arg) = directive.arguments.named(CONSTANTS.query_name_arg) {
                if let Some(query_name) = query_name_arg.value.item.get_string_literal() {
                    if let Some(previous_fragment) = self
                        .existing_refetch_operations
                        .insert(query_name, fragment.name)
                    {
                        Err(vec![ValidationError::new(
                            ValidationMessage::DuplicateRefetchableOperation {
                                query_name,
                                fragment_name: fragment.name.item,
                                previous_fragment_name: previous_fragment.item,
                            },
                            vec![fragment.name.location, previous_fragment.location],
                        )])
                    } else {
                        Ok(Some(query_name))
                    }
                } else {
                    Err(vec![ValidationError::new(
                        ValidationMessage::ExpectQueryNameToBeString {
                            query_name_value: print_value(
                                &self.program.schema,
                                &query_name_arg.value.item,
                            ),
                        },
                        vec![query_name_arg.name.location],
                    )])
                }
            } else {
                Err(vec![ValidationError::new(
                    ValidationMessage::QueryNameRequired,
                    vec![directive.name.location],
                )])
            }
        } else {
            Ok(None)
        }
    }

    /// Validate that any @connection usage is valid for refetching:
    /// - Variables are used for both the "count" and "cursor" arguments
    ///   (after/first or before/last)
    /// - Exactly one connection
    /// - Has a stable path to the connection data
    ///
    /// Connection metadata is extracted in `transform_connection`
    fn validate_connection_metadata(&self, fragment: &FragmentDefinition) -> ValidationResult<()> {
        if let Some(metadatas) = extract_connection_metadata_from_directive(
            &fragment.directives,
            self.connection_constants,
        ) {
            // TODO: path or connection field locations in the error messages
            if metadatas.len() > 1 {
                return Err(vec![ValidationError::new(
                    ValidationMessage::RefetchableWithMultipleConnections {
                        fragment_name: fragment.name.item,
                    },
                    vec![fragment.name.location],
                )]);
            } else if metadatas.len() == 1 {
                let metadata = &metadatas[0];
                if metadata.path.is_none() {
                    return Err(vec![ValidationError::new(
                        ValidationMessage::RefetchableWithConnectionInPlural {
                            fragment_name: fragment.name.item,
                        },
                        vec![fragment.name.location],
                    )]);
                }
                if (metadata.after.is_none() || metadata.first.is_none())
                    && metadata.direction != self.connection_constants.direction_backward
                {
                    return Err(vec![ValidationError::new(
                        ValidationMessage::RefetchableWithConstConnectionArguments {
                            fragment_name: fragment.name.item,
                            arguments: "after and first",
                        },
                        vec![fragment.name.location],
                    )]);
                } else if (metadata.before.is_none() || metadata.last.is_none())
                    && metadata.direction != self.connection_constants.direction_forward
                {
                    return Err(vec![ValidationError::new(
                        ValidationMessage::RefetchableWithConstConnectionArguments {
                            fragment_name: fragment.name.item,
                            arguments: "before and last",
                        },
                        vec![fragment.name.location],
                    )]);
                }
            }
        }
        Ok(())
    }
}

type BuildRefetchOperationFn = fn(
    schema: &Schema,
    fragment: &Arc<FragmentDefinition>,
    query_name: StringKey,
    variables_map: &VariableMap,
) -> ValidationResult<Option<RefetchRoot>>;
/// A strategy to generate queries for a given fragment. Multiple stategies
/// can be tried, such as generating a `node(id: ID)` query or a query directly
/// on the root query type.
pub struct QueryGenerator {
    /// Used to describe what fragments this QueryGenerator applies to, used in
    /// error messages.
    pub description: &'static str,

    /// Returns RefetchRoot or null if not applicable. Might throw a user error
    /// for an invalid schema or other problems.
    pub build_refetch_operation: BuildRefetchOperationFn,
}

const GENERATORS: [QueryGenerator; 4] = [
    VIEWER_QUERY_GENERATOR,
    QUERY_QUERY_GENERATOR,
    NODE_QUERY_GENERATOR,
    FETCHABLE_QUERY_GENERATOR,
];

#[allow(dead_code)]
pub struct RefetchRoot {
    operation: Arc<OperationDefinition>,
    fragment: Arc<FragmentDefinition>,
}
<commit_msg>- run transform for base fragments<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

mod fetchable_query_generator;
mod node_query_generator;
mod query_query_generator;
mod utils;
mod viewer_query_generator;

use crate::connections::{extract_connection_metadata_from_directive, ConnectionConstants};
use crate::root_variables::{InferVariablesVisitor, VariableMap};

use common::{NamedItem, WithLocation};
use errors::validate_map;
use fetchable_query_generator::FETCHABLE_QUERY_GENERATOR;
use fnv::{FnvHashMap, FnvHashSet};
use graphql_ir::{
    FragmentDefinition, OperationDefinition, Program, ValidationError, ValidationMessage,
    ValidationResult,
};
use graphql_text_printer::print_value;
use interner::StringKey;
use node_query_generator::NODE_QUERY_GENERATOR;
use query_query_generator::QUERY_QUERY_GENERATOR;
use schema::Schema;
use std::fmt::Write;
use std::sync::Arc;
use utils::*;
pub use utils::{
    extract_refetch_metadata_from_directive, RefetchableDerivedFromMetadata, CONSTANTS,
};
use viewer_query_generator::VIEWER_QUERY_GENERATOR;

/// This transform synthesizes "refetch" queries for fragments that
/// are trivially refetchable. This is comprised of three main stages:
///
/// 1. Validating that fragments marked with @refetchable qualify for
///    refetch query generation; mainly this means that the fragment
///    type is able to be refetched in some canonical way.
/// 2. Determining the variable definitions to use for each generated
///    query. GraphQL does not have a notion of fragment-local variables
///    at all, and although Relay adds this concept developers are still
///    allowed to reference global variables. This necessitates a
///    visiting all reachable fragments for each @refetchable fragment,
///    and finding the union of all global variables expceted to be defined.
/// 3. Building the refetch queries, a straightforward copying transform from
///    Fragment to Root IR nodes.
pub fn transform_refetchable_fragment(
    program: &Program,
    base_fragment_names: &'_ FnvHashSet<StringKey>,
    for_typegen: bool,
) -> ValidationResult<Program> {
    let mut next_program = Program::new(Arc::clone(&program.schema));

    let mut transformer = RefetchableFragment {
        connection_constants: Default::default(),
        existing_refetch_operations: Default::default(),
        for_typegen,
        program,
        visitor: InferVariablesVisitor::new(program),
    };

    for operation in program.operations() {
        next_program.insert_operation(Arc::clone(operation));
    }

    validate_map(program.fragments(), |fragment| {
        let operation_result = transformer.transform_refetch_fragment(fragment)?;
        if let Some(operation_result) = operation_result {
            next_program.insert_fragment(operation_result.fragment);
            if !base_fragment_names.contains(&fragment.name.item) {
                next_program.insert_operation(operation_result.operation);
            }
        } else {
            next_program.insert_fragment(Arc::clone(fragment));
        }
        Ok(())
    })?;

    Ok(next_program)
}

type ExistingRefetchOperations = FnvHashMap<StringKey, WithLocation<StringKey>>;

struct RefetchableFragment<'program> {
    connection_constants: ConnectionConstants,
    existing_refetch_operations: ExistingRefetchOperations,
    for_typegen: bool,
    program: &'program Program,
    visitor: InferVariablesVisitor<'program>,
}

impl RefetchableFragment<'_> {
    fn transform_refetch_fragment(
        &mut self,
        fragment: &Arc<FragmentDefinition>,
    ) -> ValidationResult<Option<RefetchRoot>> {
        let refetch_name = self.get_refetch_query_name(fragment)?;
        if let Some(refetch_name) = refetch_name {
            let variables_map = self.visitor.infer_fragment_variables(fragment);
            for generator in GENERATORS.iter() {
                if let Some(refetch_root) = (generator.build_refetch_operation)(
                    &self.program.schema,
                    fragment,
                    refetch_name,
                    &variables_map,
                )? {
                    if !self.for_typegen {
                        self.validate_connection_metadata(refetch_root.fragment.as_ref())?;
                    }
                    return Ok(Some(refetch_root));
                }
            }
            let mut descriptions = String::new();
            for generator in GENERATORS.iter() {
                writeln!(descriptions, " - {}", generator.description).unwrap();
            }
            descriptions.pop();
            Err(vec![ValidationError::new(
                ValidationMessage::UnsupportedRefetchableFragment {
                    fragment_name: fragment.name.item,
                    descriptions,
                },
                vec![fragment.name.location],
            )])
        } else {
            Ok(None)
        }
    }

    fn get_refetch_query_name(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> ValidationResult<Option<StringKey>> {
        if let Some(directive) = fragment.directives.named(CONSTANTS.refetchable_name) {
            if let Some(query_name_arg) = directive.arguments.named(CONSTANTS.query_name_arg) {
                if let Some(query_name) = query_name_arg.value.item.get_string_literal() {
                    if let Some(previous_fragment) = self
                        .existing_refetch_operations
                        .insert(query_name, fragment.name)
                    {
                        Err(vec![ValidationError::new(
                            ValidationMessage::DuplicateRefetchableOperation {
                                query_name,
                                fragment_name: fragment.name.item,
                                previous_fragment_name: previous_fragment.item,
                            },
                            vec![fragment.name.location, previous_fragment.location],
                        )])
                    } else {
                        Ok(Some(query_name))
                    }
                } else {
                    Err(vec![ValidationError::new(
                        ValidationMessage::ExpectQueryNameToBeString {
                            query_name_value: print_value(
                                &self.program.schema,
                                &query_name_arg.value.item,
                            ),
                        },
                        vec![query_name_arg.name.location],
                    )])
                }
            } else {
                Err(vec![ValidationError::new(
                    ValidationMessage::QueryNameRequired,
                    vec![directive.name.location],
                )])
            }
        } else {
            Ok(None)
        }
    }

    /// Validate that any @connection usage is valid for refetching:
    /// - Variables are used for both the "count" and "cursor" arguments
    ///   (after/first or before/last)
    /// - Exactly one connection
    /// - Has a stable path to the connection data
    ///
    /// Connection metadata is extracted in `transform_connection`
    fn validate_connection_metadata(&self, fragment: &FragmentDefinition) -> ValidationResult<()> {
        if let Some(metadatas) = extract_connection_metadata_from_directive(
            &fragment.directives,
            self.connection_constants,
        ) {
            // TODO: path or connection field locations in the error messages
            if metadatas.len() > 1 {
                return Err(vec![ValidationError::new(
                    ValidationMessage::RefetchableWithMultipleConnections {
                        fragment_name: fragment.name.item,
                    },
                    vec![fragment.name.location],
                )]);
            } else if metadatas.len() == 1 {
                let metadata = &metadatas[0];
                if metadata.path.is_none() {
                    return Err(vec![ValidationError::new(
                        ValidationMessage::RefetchableWithConnectionInPlural {
                            fragment_name: fragment.name.item,
                        },
                        vec![fragment.name.location],
                    )]);
                }
                if (metadata.after.is_none() || metadata.first.is_none())
                    && metadata.direction != self.connection_constants.direction_backward
                {
                    return Err(vec![ValidationError::new(
                        ValidationMessage::RefetchableWithConstConnectionArguments {
                            fragment_name: fragment.name.item,
                            arguments: "after and first",
                        },
                        vec![fragment.name.location],
                    )]);
                } else if (metadata.before.is_none() || metadata.last.is_none())
                    && metadata.direction != self.connection_constants.direction_forward
                {
                    return Err(vec![ValidationError::new(
                        ValidationMessage::RefetchableWithConstConnectionArguments {
                            fragment_name: fragment.name.item,
                            arguments: "before and last",
                        },
                        vec![fragment.name.location],
                    )]);
                }
            }
        }
        Ok(())
    }
}

type BuildRefetchOperationFn = fn(
    schema: &Schema,
    fragment: &Arc<FragmentDefinition>,
    query_name: StringKey,
    variables_map: &VariableMap,
) -> ValidationResult<Option<RefetchRoot>>;
/// A strategy to generate queries for a given fragment. Multiple stategies
/// can be tried, such as generating a `node(id: ID)` query or a query directly
/// on the root query type.
pub struct QueryGenerator {
    /// Used to describe what fragments this QueryGenerator applies to, used in
    /// error messages.
    pub description: &'static str,

    /// Returns RefetchRoot or null if not applicable. Might throw a user error
    /// for an invalid schema or other problems.
    pub build_refetch_operation: BuildRefetchOperationFn,
}

const GENERATORS: [QueryGenerator; 4] = [
    VIEWER_QUERY_GENERATOR,
    QUERY_QUERY_GENERATOR,
    NODE_QUERY_GENERATOR,
    FETCHABLE_QUERY_GENERATOR,
];

#[allow(dead_code)]
pub struct RefetchRoot {
    operation: Arc<OperationDefinition>,
    fragment: Arc<FragmentDefinition>,
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#![allow(unsafe_code)]

use crate::sampler::{NativeStack, Sampler};
use libc;
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
use std::cell::UnsafeCell;
use std::io;
use std::mem;
use std::process;
use std::ptr;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::thread;
use unwind_sys::{
    unw_cursor_t, unw_get_reg, unw_init_local, unw_step, UNW_ESUCCESS, UNW_REG_IP, UNW_REG_SP,
};

// Hack to workaround broken libunwind pkg-config contents for <1.1-3ubuntu.1.
// https://bugs.launchpad.net/ubuntu/+source/libunwind/+bug/1336912
#[link(name = "lzma")]
extern "C" {}

static mut SHARED_STATE: SharedState = SharedState {
    msg2: None,
    msg3: None,
    msg4: None,
    context: AtomicPtr::new(ptr::null_mut()),
};

type MonitoredThreadId = libc::pid_t;

struct SharedState {
    // "msg1" is the signal.
    msg2: Option<PosixSemaphore>,
    msg3: Option<PosixSemaphore>,
    msg4: Option<PosixSemaphore>,
    context: AtomicPtr<libc::ucontext_t>,
}

fn clear_shared_state() {
    unsafe {
        SHARED_STATE.msg2 = None;
        SHARED_STATE.msg3 = None;
        SHARED_STATE.msg4 = None;
        SHARED_STATE.context = AtomicPtr::new(ptr::null_mut());
    }
}

fn reset_shared_state() {
    unsafe {
        SHARED_STATE.msg2 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
        SHARED_STATE.msg3 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
        SHARED_STATE.msg4 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
        SHARED_STATE.context = AtomicPtr::new(ptr::null_mut());
    }
}

struct PosixSemaphore {
    sem: UnsafeCell<libc::sem_t>,
}

impl PosixSemaphore {
    pub fn new(value: u32) -> io::Result<Self> {
        let mut sem = mem::MaybeUninit::uninit();
        let r = unsafe {
            libc::sem_init(sem.as_mut_ptr(), 0 /* not shared */, value)
        };
        if r == -1 {
            return Err(io::Error::last_os_error());
        }
        Ok(PosixSemaphore {
            sem: UnsafeCell::new(unsafe { sem.assume_init() }),
        })
    }

    pub fn post(&self) -> io::Result<()> {
        if unsafe { libc::sem_post(self.sem.get()) } == 0 {
            Ok(())
        } else {
            Err(io::Error::last_os_error())
        }
    }

    pub fn wait(&self) -> io::Result<()> {
        if unsafe { libc::sem_wait(self.sem.get()) } == 0 {
            Ok(())
        } else {
            Err(io::Error::last_os_error())
        }
    }

    /// Retries the wait if it returned due to EINTR.
    /// Returns Ok on success and the error on any other return value.
    pub fn wait_through_intr(&self) -> io::Result<()> {
        loop {
            match self.wait() {
                Err(os_error) => {
                    let err = os_error.raw_os_error().expect("no os error");
                    if err == libc::EINTR {
                        thread::yield_now();
                        continue;
                    }
                    return Err(os_error);
                },
                _ => return Ok(()),
            }
        }
    }
}

unsafe impl Sync for PosixSemaphore {}

impl Drop for PosixSemaphore {
    /// Destroys the semaphore.
    fn drop(&mut self) {
        unsafe { libc::sem_destroy(self.sem.get()) };
    }
}

#[allow(dead_code)]
pub struct LinuxSampler {
    thread_id: MonitoredThreadId,
    old_handler: SigAction,
}

impl LinuxSampler {
    #[allow(unsafe_code, dead_code)]
    pub fn new() -> Box<dyn Sampler> {
        let thread_id = unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t };
        let handler = SigHandler::SigAction(sigprof_handler);
        let action = SigAction::new(
            handler,
            SaFlags::SA_RESTART | SaFlags::SA_SIGINFO,
            SigSet::empty(),
        );
        let old_handler =
            unsafe { sigaction(Signal::SIGPROF, &action).expect("signal handler set") };
        Box::new(LinuxSampler {
            thread_id,
            old_handler,
        })
    }
}

enum RegNum {
    Ip = UNW_REG_IP as isize,
    Sp = UNW_REG_SP as isize,
}

fn get_register(cursor: *mut unw_cursor_t, num: RegNum) -> Result<u64, i32> {
    unsafe {
        let mut val = 0;
        let ret = unw_get_reg(cursor, num as i32, &mut val);
        if ret == UNW_ESUCCESS {
            Ok(val)
        } else {
            Err(ret)
        }
    }
}

fn step(cursor: *mut unw_cursor_t) -> Result<bool, i32> {
    unsafe {
        // libunwind 1.1 seems to get confused and walks off the end of the stack. The last IP
        // it reports is 0, so we'll stop if we're there.
        if get_register(cursor, RegNum::Ip).unwrap_or(1) == 0 {
            return Ok(false);
        }

        let ret = unw_step(cursor);
        if ret > 0 {
            Ok(true)
        } else if ret == 0 {
            Ok(false)
        } else {
            Err(ret)
        }
    }
}

impl Sampler for LinuxSampler {
    #[allow(unsafe_code)]
    fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
        // Warning: The "critical section" begins here.
        // In the critical section:
        // we must not do any dynamic memory allocation,
        // nor try to acquire any lock
        // or any other unshareable resource.
        // first we reinitialize the semaphores
        reset_shared_state();

        // signal the thread, wait for it to tell us state was copied.
        send_sigprof(self.thread_id);
        unsafe {
            SHARED_STATE
                .msg2
                .as_ref()
                .unwrap()
                .wait_through_intr()
                .expect("msg2 failed");
        }

        let context = unsafe { SHARED_STATE.context.load(Ordering::SeqCst) };
        let mut cursor = mem::MaybeUninit::uninit();
        let ret = unsafe { unw_init_local(cursor.as_mut_ptr(), context) };
        let result = if ret == UNW_ESUCCESS {
            let mut native_stack = NativeStack::new();
            loop {
                let ip = match get_register(cursor.as_mut_ptr(), RegNum::Ip) {
                    Ok(ip) => ip,
                    Err(_) => break,
                };
                let sp = match get_register(cursor.as_mut_ptr(), RegNum::Sp) {
                    Ok(sp) => sp,
                    Err(_) => break,
                };
                if native_stack
                    .process_register(ip as *mut _, sp as *mut _)
                    .is_err() ||
                    !step(cursor.as_mut_ptr()).unwrap_or(false)
                {
                    break;
                }
            }
            Ok(native_stack)
        } else {
            Err(())
        };

        // signal the thread to continue.
        unsafe {
            SHARED_STATE
                .msg3
                .as_ref()
                .unwrap()
                .post()
                .expect("msg3 failed");
        }

        // wait for thread to continue.
        unsafe {
            SHARED_STATE
                .msg4
                .as_ref()
                .unwrap()
                .wait_through_intr()
                .expect("msg4 failed");
        }

        clear_shared_state();

        // NOTE: End of "critical section".
        result
    }
}

impl Drop for LinuxSampler {
    fn drop(&mut self) {
        unsafe {
            sigaction(Signal::SIGPROF, &self.old_handler).expect("previous signal handler restored")
        };
    }
}

extern "C" fn sigprof_handler(
    sig: libc::c_int,
    _info: *mut libc::siginfo_t,
    ctx: *mut libc::c_void,
) {
    assert_eq!(sig, libc::SIGPROF);
    unsafe {
        // copy the context.
        SHARED_STATE
            .context
            .store(ctx as *mut libc::ucontext_t, Ordering::SeqCst);
        // Tell the sampler we copied the context.
        SHARED_STATE.msg2.as_ref().unwrap().post().expect("posted");

        // Wait for sampling to finish.
        SHARED_STATE
            .msg3
            .as_ref()
            .unwrap()
            .wait_through_intr()
            .expect("msg3 wait succeeded");

        // OK we are done!
        SHARED_STATE.msg4.as_ref().unwrap().post().expect("posted");
        // DO NOT TOUCH shared state here onwards.
    }
}

fn send_sigprof(to: libc::pid_t) {
    unsafe {
        libc::syscall(libc::SYS_tgkill, process::id(), to, libc::SIGPROF);
    }
}
<commit_msg>Use UnsafeCell instead of `static mut` in background_hang_monitor/sampler_linux.rs<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#![allow(unsafe_code)]

use crate::sampler::{NativeStack, Sampler};
use libc;
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
use std::cell::UnsafeCell;
use std::io;
use std::mem;
use std::process;
use std::ptr;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::thread;
use unwind_sys::{
    unw_cursor_t, unw_get_reg, unw_init_local, unw_step, UNW_ESUCCESS, UNW_REG_IP, UNW_REG_SP,
};

// Hack to workaround broken libunwind pkg-config contents for <1.1-3ubuntu.1.
// https://bugs.launchpad.net/ubuntu/+source/libunwind/+bug/1336912
#[link(name = "lzma")]
extern "C" {}

struct UncheckedSyncUnsafeCell<T>(std::cell::UnsafeCell<T>);

/// Safety: dereferencing the pointer from `UnsafeCell::get` must involve external synchronization
unsafe impl<T> Sync for UncheckedSyncUnsafeCell<T> {}

static SHARED_STATE: UncheckedSyncUnsafeCell<SharedState> =
    UncheckedSyncUnsafeCell(std::cell::UnsafeCell::new(SharedState {
        msg2: None,
        msg3: None,
        msg4: None,
    }));

static CONTEXT: AtomicPtr<libc::ucontext_t> = AtomicPtr::new(ptr::null_mut());

type MonitoredThreadId = libc::pid_t;

struct SharedState {
    // "msg1" is the signal.
    msg2: Option<PosixSemaphore>,
    msg3: Option<PosixSemaphore>,
    msg4: Option<PosixSemaphore>,
}

fn clear_shared_state() {
    // Safety: this is only called from the sampling thread (there’s only one)
    // Sampled threads only access SHARED_STATE in their signal handler.
    // This signal and the semaphores in SHARED_STATE provide the necessary synchronization.
    unsafe {
        let shared_state = &mut *SHARED_STATE.0.get();
        shared_state.msg2 = None;
        shared_state.msg3 = None;
        shared_state.msg4 = None;
    }
    CONTEXT.store(ptr::null_mut(), Ordering::SeqCst);
}

fn reset_shared_state() {
    // Safety: same as clear_shared_state
    unsafe {
        let shared_state = &mut *SHARED_STATE.0.get();
        shared_state.msg2 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
        shared_state.msg3 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
        shared_state.msg4 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
    }
    CONTEXT.store(ptr::null_mut(), Ordering::SeqCst);
}

struct PosixSemaphore {
    sem: UnsafeCell<libc::sem_t>,
}

impl PosixSemaphore {
    pub fn new(value: u32) -> io::Result<Self> {
        let mut sem = mem::MaybeUninit::uninit();
        let r = unsafe {
            libc::sem_init(sem.as_mut_ptr(), 0 /* not shared */, value)
        };
        if r == -1 {
            return Err(io::Error::last_os_error());
        }
        Ok(PosixSemaphore {
            sem: UnsafeCell::new(unsafe { sem.assume_init() }),
        })
    }

    pub fn post(&self) -> io::Result<()> {
        if unsafe { libc::sem_post(self.sem.get()) } == 0 {
            Ok(())
        } else {
            Err(io::Error::last_os_error())
        }
    }

    pub fn wait(&self) -> io::Result<()> {
        if unsafe { libc::sem_wait(self.sem.get()) } == 0 {
            Ok(())
        } else {
            Err(io::Error::last_os_error())
        }
    }

    /// Retries the wait if it returned due to EINTR.
    /// Returns Ok on success and the error on any other return value.
    pub fn wait_through_intr(&self) -> io::Result<()> {
        loop {
            match self.wait() {
                Err(os_error) => {
                    let err = os_error.raw_os_error().expect("no os error");
                    if err == libc::EINTR {
                        thread::yield_now();
                        continue;
                    }
                    return Err(os_error);
                },
                _ => return Ok(()),
            }
        }
    }
}

unsafe impl Sync for PosixSemaphore {}

impl Drop for PosixSemaphore {
    /// Destroys the semaphore.
    fn drop(&mut self) {
        unsafe { libc::sem_destroy(self.sem.get()) };
    }
}

#[allow(dead_code)]
pub struct LinuxSampler {
    thread_id: MonitoredThreadId,
    old_handler: SigAction,
}

impl LinuxSampler {
    #[allow(unsafe_code, dead_code)]
    pub fn new() -> Box<dyn Sampler> {
        let thread_id = unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t };
        let handler = SigHandler::SigAction(sigprof_handler);
        let action = SigAction::new(
            handler,
            SaFlags::SA_RESTART | SaFlags::SA_SIGINFO,
            SigSet::empty(),
        );
        let old_handler =
            unsafe { sigaction(Signal::SIGPROF, &action).expect("signal handler set") };
        Box::new(LinuxSampler {
            thread_id,
            old_handler,
        })
    }
}

enum RegNum {
    Ip = UNW_REG_IP as isize,
    Sp = UNW_REG_SP as isize,
}

fn get_register(cursor: *mut unw_cursor_t, num: RegNum) -> Result<u64, i32> {
    unsafe {
        let mut val = 0;
        let ret = unw_get_reg(cursor, num as i32, &mut val);
        if ret == UNW_ESUCCESS {
            Ok(val)
        } else {
            Err(ret)
        }
    }
}

fn step(cursor: *mut unw_cursor_t) -> Result<bool, i32> {
    unsafe {
        // libunwind 1.1 seems to get confused and walks off the end of the stack. The last IP
        // it reports is 0, so we'll stop if we're there.
        if get_register(cursor, RegNum::Ip).unwrap_or(1) == 0 {
            return Ok(false);
        }

        let ret = unw_step(cursor);
        if ret > 0 {
            Ok(true)
        } else if ret == 0 {
            Ok(false)
        } else {
            Err(ret)
        }
    }
}

impl Sampler for LinuxSampler {
    #[allow(unsafe_code)]
    fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
        // Warning: The "critical section" begins here.
        // In the critical section:
        // we must not do any dynamic memory allocation,
        // nor try to acquire any lock
        // or any other unshareable resource.
        // first we reinitialize the semaphores
        reset_shared_state();

        // signal the thread, wait for it to tell us state was copied.
        send_sigprof(self.thread_id);

        // Safety: non-exclusive reference only
        // since sampled threads are accessing this concurrently
        let shared_state = unsafe { &*SHARED_STATE.0.get() };
        shared_state
            .msg2
            .as_ref()
            .unwrap()
            .wait_through_intr()
            .expect("msg2 failed");

        let context = CONTEXT.load(Ordering::SeqCst);
        let mut cursor = mem::MaybeUninit::uninit();
        let ret = unsafe { unw_init_local(cursor.as_mut_ptr(), context) };
        let result = if ret == UNW_ESUCCESS {
            let mut native_stack = NativeStack::new();
            loop {
                let ip = match get_register(cursor.as_mut_ptr(), RegNum::Ip) {
                    Ok(ip) => ip,
                    Err(_) => break,
                };
                let sp = match get_register(cursor.as_mut_ptr(), RegNum::Sp) {
                    Ok(sp) => sp,
                    Err(_) => break,
                };
                if native_stack
                    .process_register(ip as *mut _, sp as *mut _)
                    .is_err() ||
                    !step(cursor.as_mut_ptr()).unwrap_or(false)
                {
                    break;
                }
            }
            Ok(native_stack)
        } else {
            Err(())
        };

        // signal the thread to continue.
        shared_state
            .msg3
            .as_ref()
            .unwrap()
            .post()
            .expect("msg3 failed");

        // wait for thread to continue.
        shared_state
            .msg4
            .as_ref()
            .unwrap()
            .wait_through_intr()
            .expect("msg4 failed");

        // No-op, but marks the end of the shared borrow
        drop(shared_state);

        clear_shared_state();

        // NOTE: End of "critical section".
        result
    }
}

impl Drop for LinuxSampler {
    fn drop(&mut self) {
        unsafe {
            sigaction(Signal::SIGPROF, &self.old_handler).expect("previous signal handler restored")
        };
    }
}

extern "C" fn sigprof_handler(
    sig: libc::c_int,
    _info: *mut libc::siginfo_t,
    ctx: *mut libc::c_void,
) {
    assert_eq!(sig, libc::SIGPROF);
    // copy the context.
    CONTEXT.store(ctx as *mut libc::ucontext_t, Ordering::SeqCst);

    // Safety: non-exclusive reference only
    // since the sampling thread is accessing this concurrently
    let shared_state = unsafe { &*SHARED_STATE.0.get() };

    // Tell the sampler we copied the context.
    shared_state.msg2.as_ref().unwrap().post().expect("posted");

    // Wait for sampling to finish.
    shared_state
        .msg3
        .as_ref()
        .unwrap()
        .wait_through_intr()
        .expect("msg3 wait succeeded");

    // OK we are done!
    shared_state.msg4.as_ref().unwrap().post().expect("posted");
    // DO NOT TOUCH shared state here onwards.
}

fn send_sigprof(to: libc::pid_t) {
    unsafe {
        libc::syscall(libc::SYS_tgkill, process::id(), to, libc::SIGPROF);
    }
}
<|endoftext|>