<commit_before>use crate::exercise::{Exercise, ExerciseList};
use crate::run::run;
use crate::verify::verify;
use clap::{crate_version, App, Arg, SubCommand};
use notify::DebouncedEvent;
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
use std::time::Duration;

mod exercise;
mod run;
mod verify;

fn main() {
    let matches = App::new("rustlings")
        .version(crate_version!())
        .author("Olivia Hugger, Carol Nichols")
        .about("Rustlings is a collection of small exercises to get you used to writing and reading Rust code")
        .subcommand(SubCommand::with_name("verify").alias("v").about("Verifies all exercises according to the recommended order"))
        .subcommand(SubCommand::with_name("watch").alias("w").about("Reruns `verify` when files were edited"))
        .subcommand(
            SubCommand::with_name("run")
                .alias("r")
                .about("Runs/Tests a single exercise")
                .arg(Arg::with_name("file").required(true).index(1))
                .arg(Arg::with_name("test").short("t").long("test").help("Run the file as a test")),
        )
        .get_matches();

    if None == matches.subcommand_name() {
        println!();
        println!(r#"       welcome to...                      "#);
        println!(r#"                 _   _ _                  "#);
        println!(r#"  _ __ _   _ ___| |_| (_)_ __   __ _ ___  "#);
        println!(r#" | '__| | | / __| __| | | '_ \ / _` / __| "#);
        println!(r#" | |  | |_| \__ \ |_| | | | | | (_| \__ \ "#);
        println!(r#" |_|   \__,_|___/\__|_|_|_| |_|\__, |___/ "#);
        println!(r#"                               |___/      "#);
        println!();
    }

    if !Path::new("info.toml").exists() {
        println!(
            "{} must be run from the rustlings directory",
            std::env::current_exe().unwrap().to_str().unwrap()
        );
        println!("Try `cd rustlings/`!");
        std::process::exit(1);
    }

    let toml_str = &fs::read_to_string("info.toml").unwrap();
    let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;

    if let Some(ref matches) = matches.subcommand_matches("run") {
        let filename = matches.value_of("file").unwrap_or_else(|| {
            println!("Please supply a file name!");
            std::process::exit(1);
        });

        let matching_exercise = |e: &&Exercise| {
            Path::new(filename)
                .canonicalize()
                .map(|p| p.ends_with(&e.path))
                .unwrap_or(false)
        };

        let exercise = exercises.iter().find(matching_exercise).unwrap_or_else(|| {
            println!("No exercise found for your file name!");
            std::process::exit(1)
        });

        run(&exercise).unwrap_or_else(|_| std::process::exit(1));
    }

    if matches.subcommand_matches("verify").is_some() {
        verify(&exercises).unwrap_or_else(|_| std::process::exit(1));
    }

    if matches.subcommand_matches("watch").is_some() {
        /* Clears the terminal with an ANSI escape code.
           Works in UNIX and newer Windows terminals. */
        println!("\x1Bc");
        watch(&exercises).unwrap();
    }

    if matches.subcommand_name().is_none() {
        let text = fs::read_to_string("default_out.txt").unwrap();
        println!("{}", text);
    }
}

fn watch(exercises: &[Exercise]) -> notify::Result<()> {
    let (tx, rx) = channel();

    let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
    watcher.watch(Path::new("./exercises"), RecursiveMode::Recursive)?;

    let _ignored = verify(exercises.iter());

    loop {
        match rx.recv() {
            Ok(event) => match event {
                DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => {
                    if b.extension() == Some(OsStr::new("rs")) && b.exists() {
                        println!("----------**********----------\n");
                        let filepath = b.as_path().canonicalize().unwrap();
                        let exercise = exercises
                            .iter()
                            .skip_while(|e| !filepath.ends_with(&e.path));
                        let _ignored = verify(exercise);
                    }
                }
                _ => {}
            },
            Err(e) => println!("watch error: {:?}", e),
        }
    }
}
<commit_msg>improvement(watch): clear screen before each `verify()`<commit_after>use crate::exercise::{Exercise, ExerciseList};
use crate::run::run;
use crate::verify::verify;
use clap::{crate_version, App, Arg, SubCommand};
use notify::DebouncedEvent;
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
use std::time::Duration;

mod exercise;
mod run;
mod verify;

fn main() {
    let matches = App::new("rustlings")
        .version(crate_version!())
        .author("Olivia Hugger, Carol Nichols")
        .about("Rustlings is a collection of small exercises to get you used to writing and reading Rust code")
        .subcommand(SubCommand::with_name("verify").alias("v").about("Verifies all exercises according to the recommended order"))
        .subcommand(SubCommand::with_name("watch").alias("w").about("Reruns `verify` when files were edited"))
        .subcommand(
            SubCommand::with_name("run")
                .alias("r")
                .about("Runs/Tests a single exercise")
                .arg(Arg::with_name("file").required(true).index(1))
                .arg(Arg::with_name("test").short("t").long("test").help("Run the file as a test")),
        )
        .get_matches();

    if None == matches.subcommand_name() {
        println!();
        println!(r#"       welcome to...                      "#);
        println!(r#"                 _   _ _                  "#);
        println!(r#"  _ __ _   _ ___| |_| (_)_ __   __ _ ___  "#);
        println!(r#" | '__| | | / __| __| | | '_ \ / _` / __| "#);
        println!(r#" | |  | |_| \__ \ |_| | | | | | (_| \__ \ "#);
        println!(r#" |_|   \__,_|___/\__|_|_|_| |_|\__, |___/ "#);
        println!(r#"                               |___/      "#);
        println!();
    }

    if !Path::new("info.toml").exists() {
        println!(
            "{} must be run from the rustlings directory",
            std::env::current_exe().unwrap().to_str().unwrap()
        );
        println!("Try `cd rustlings/`!");
        std::process::exit(1);
    }

    let toml_str = &fs::read_to_string("info.toml").unwrap();
    let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;

    if let Some(ref matches) = matches.subcommand_matches("run") {
        let filename = matches.value_of("file").unwrap_or_else(|| {
            println!("Please supply a file name!");
            std::process::exit(1);
        });

        let matching_exercise = |e: &&Exercise| {
            Path::new(filename)
                .canonicalize()
                .map(|p| p.ends_with(&e.path))
                .unwrap_or(false)
        };

        let exercise = exercises.iter().find(matching_exercise).unwrap_or_else(|| {
            println!("No exercise found for your file name!");
            std::process::exit(1)
        });

        run(&exercise).unwrap_or_else(|_| std::process::exit(1));
    }

    if matches.subcommand_matches("verify").is_some() {
        verify(&exercises).unwrap_or_else(|_| std::process::exit(1));
    }

    if matches.subcommand_matches("watch").is_some() {
        watch(&exercises).unwrap();
    }

    if matches.subcommand_name().is_none() {
        let text = fs::read_to_string("default_out.txt").unwrap();
        println!("{}", text);
    }
}

fn watch(exercises: &[Exercise]) -> notify::Result<()> {
    /* Clears the terminal with an ANSI escape code.
    Works in UNIX and newer Windows terminals. */
    fn clear_screen() {
        println!("\x1Bc");
    }

    let (tx, rx) = channel();

    let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
    watcher.watch(Path::new("./exercises"), RecursiveMode::Recursive)?;

    clear_screen();
    let _ignored = verify(exercises.iter());

    loop {
        match rx.recv() {
            Ok(event) => match event {
                DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => {
                    if b.extension() == Some(OsStr::new("rs")) && b.exists() {
                        let filepath = b.as_path().canonicalize().unwrap();
                        let exercise = exercises
                            .iter()
                            .skip_while(|e| !filepath.ends_with(&e.path));
                        clear_screen();
                        let _ignored = verify(exercise);
                    }
                }
                _ => {}
            },
            Err(e) => println!("watch error: {:?}", e),
        }
    }
}
<|endoftext|>


<commit_before>#![feature(const_size_of)]
extern crate byteorder;

use std::io;
use std::io::prelude::*;
use std::cmp;
use std::fmt;
use std::str;
use std::env;
use std::fs::{File, OpenOptions};
use byteorder::{ReadBytesExt, WriteBytesExt, LE};

const MAX_UNAME_LENGTH: usize = 32;
const MAX_EMAIL_LENGTH: usize = 256;

#[derive(Copy)]
struct Row {
    id: u32,
    username_len: u32,
    username: [u8; MAX_UNAME_LENGTH],
    email_len: u32,
    email: [u8; MAX_EMAIL_LENGTH],
}
const ROW_SIZE: usize = std::mem::size_of::<Row>();

#[derive(Debug)]
enum PagerError {
    OutOfBounds { page_num: usize, max_pages: usize },
    CouldNotRead,
}

struct Pager {
    // f: io::BufReader<File>,
    fd: File,
    file_size: u64,
    pages: [Option<Box<Page>>; TABLE_MAX_PAGES],
}

impl Pager {
    fn new(f: File, file_size: u64) -> Self {
        Pager {
            fd: f,
            file_size,
            pages: {
                let mut array: [Option<Box<Page>>; 100] = unsafe { std::mem::uninitialized() };
                for i in array.iter_mut() {
                    unsafe {
                        ::std::ptr::write(i, None);
                    }
                }
                array
            },
        }
    }

    fn open(filename: &str) -> Result<Box<Self>, io::Error> {
        let mut f = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(filename)?;
        let file_size = f.seek(io::SeekFrom::End(0))
            .expect("Could not read to end of file");
        let pager = Box::new(Pager::new(f, file_size));
        Ok(pager)
    }

    fn read_into_row<R: Read>(&self, reader: &mut R, row: &mut Row) -> io::Result<()> {
        row.id = reader.read_u32::<LE>()?;
        row.username_len = reader.read_u32::<LE>()?;
        reader.read(&mut row.username)?;
        row.email_len = reader.read_u32::<LE>()?;
        reader.read(&mut row.email)?;
        Ok(())
    }

    fn get_page<'a>(&'a mut self, page_num: usize) -> Result<&'a Page, PagerError> {
        if page_num > TABLE_MAX_PAGES {
            Err(PagerError::OutOfBounds {
                page_num,
                max_pages: TABLE_MAX_PAGES,
            })
        } else {
            match self.pages[page_num] {
                Some(ref page) => Ok(page),
                None => {
                    let mut new_page = self.allocate_page(page_num as u64).unwrap();
                        // .map_err(|_| PagerError::CouldNotRead)?;
                    self.pages[page_num] = Some(new_page);
                    Ok(self.pages[page_num].as_ref().unwrap())
                }
            }
        }
    }

    fn allocate_page(&mut self, page_num: u64) -> io::Result<Box<Page>> {
        let mut new_page = Box::new(Page::new());
        let num_pages = self.file_size / PAGE_SIZE + ((self.file_size % PAGE_SIZE != 0) as u64);

        if (page_num as u64) < num_pages {
            self.fd.seek(io::SeekFrom::Start(page_num as u64 * PAGE_SIZE))?;
            let mut row_bytes = [0u8; ROW_SIZE];
            for row in new_page.rows.iter_mut() {
                match self.fd.read(&mut row_bytes) {
                    Ok(ROW_SIZE) => self.read_into_row(&mut row_bytes.as_ref(), row)?,
                    Ok(0) => break,
                    Ok(_) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof)),
                    Err(e) => return Err(e)
                }
            }
        }
        Ok(new_page)
    }

    fn get_page_mut<'a>(&'a mut self, page_num: usize) -> Result<&'a mut Page, PagerError> {
        match self.pages[page_num] {
            Some(ref mut page) => Ok(page),
            None => {
                let mut new_page = self.allocate_page(page_num as u64)
                    .map_err(|_| PagerError::CouldNotRead)?;
                self.pages[page_num] = Some(new_page);
                Ok(self.pages[page_num].as_mut().unwrap())
                // match self.pages[page_num as usize] {
                //     Some(ref mut page) => page.borrow_mut(),
                //     _ => unreachable!(),
                // }
            }
        }
    }

    // FIXME get rid of this
    fn flush_partial(&mut self, page_num: usize, rows: usize) -> Result<(), io::Error> {
        if let Some(ref page) = self.pages[page_num] {
            let mut out = io::BufWriter::with_capacity(ROW_SIZE * rows, &mut self.fd);
            out.seek(io::SeekFrom::Start(page_num as u64 * PAGE_SIZE))
                .unwrap();
            for r in page.rows[..rows].iter() {
                out.write_u32::<LE>(r.id)?;
                out.write_u32::<LE>(r.username_len)?;
                out.write(&r.username)?;
                out.write_u32::<LE>(r.email_len)?;
                out.write(&r.email)?;
            }
        }
        Ok(())
    }

    fn flush_page(&mut self, page_num: usize) -> Result<(), io::Error> {
        let mut out = io::BufWriter::with_capacity(PAGE_SIZE as usize, &mut self.fd);
        if let Some(ref page) = self.pages[page_num] {
            out.seek(io::SeekFrom::Start(page_num as u64 * PAGE_SIZE))
                .unwrap();
            for r in page.rows.iter() {
                out.write_u32::<LE>(r.id)?;
                out.write_u32::<LE>(r.username_len)?;
                out.write(&r.username)?;
                out.write_u32::<LE>(r.email_len)?;
                out.write(&r.email)?;
            }
        }
        Ok(())
    }
}

// impl Drop for Pager {
//     fn drop(&mut self) {
//         for i in 0..self.pages.len() {}
//     }
// }

impl Row {
    fn new(id: u32, username: &[u8], email: &[u8]) -> Self {
        let username_len = cmp::min(MAX_UNAME_LENGTH, username.len());
        let email_len = cmp::min(MAX_EMAIL_LENGTH, email.len());
        let mut row = Row {
            id: id,
            username_len: username_len as u32,
            username: [0; MAX_UNAME_LENGTH],
            email_len: email_len as u32,
            email: [0; MAX_EMAIL_LENGTH],
        };
        row.username[..username_len].copy_from_slice(&username[..username_len]);
        row.email[..email_len].copy_from_slice(&email[..email_len]);
        row
    }
}

impl Default for Row {
    fn default() -> Self {
        Row {
            id: 0,
            username_len: 0,
            username: [0; MAX_UNAME_LENGTH],
            email_len: 0,
            email: [0; MAX_EMAIL_LENGTH],
        }
    }
}

// Hack because not all fixed-size array struct members are `Clone`
impl Clone for Row {
    fn clone(&self) -> Self {
        *self
    }
}

impl fmt::Display for Row {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match (
            str::from_utf8(&self.username[..self.username_len as usize]),
            str::from_utf8(&self.email[..self.email_len as usize]),
        ) {
            (Ok(username), Ok(email)) => write!(f, "({}, {}, {})", self.id, username, email),
            _ => Err(fmt::Error),
        }
    }
}

const ROWS_PER_PAGE: usize = 4096 / std::mem::size_of::<Row>();
const TABLE_MAX_PAGES: usize = 100;
const TABLE_MAX_ROWS: u32 = (ROWS_PER_PAGE * TABLE_MAX_PAGES) as u32;
const PAGE_SIZE: u64 = std::mem::size_of::<Page>() as u64;

#[derive(Copy, Clone)]
struct Page {
    rows: [Row; ROWS_PER_PAGE],
}

impl Page {
    fn new() -> Self {
        Page {
            rows: [Default::default(); ROWS_PER_PAGE],
        }
    }

    fn get_row<'a>(&'a self, row_num: usize) -> &'a Row {
        &self.rows[row_num]
    }

    fn set_row<'a>(&'a mut self, row_num: usize, row: &Row) {
        self.rows[row_num] = *row;
    }
}

struct Table {
    pager: Box<Pager>,
    num_rows: u32,
}

impl Table {
    fn new(pager: Box<Pager>) -> Self {
        Table {
            num_rows: (pager.file_size / ROW_SIZE as u64) as u32,
            pager,
        }
    }

    fn row_slot<'a>(&'a mut self, row_num: usize) -> &'a Row {
        let (page_num, row_offset) = (row_num / ROWS_PER_PAGE, row_num % ROWS_PER_PAGE);
        let page = self.pager.get_page(page_num).unwrap();
        page.get_row(row_offset)
    }

    fn insert_row(&mut self, row: &Row, row_num: usize) -> Result<(), ExecuteError> {
        if self.num_rows >= TABLE_MAX_ROWS {
            Err(ExecuteError::TableFull)
        } else {
            let (page_num, row_offset) = (row_num / ROWS_PER_PAGE, row_num % ROWS_PER_PAGE);
            let page = self.pager.get_page_mut(page_num).unwrap();
            page.set_row(row_offset, row);
            Ok(())
        }
    }
}

enum MetaCommand {
    Exit,
}

fn do_meta_command(command: &str) -> Result<MetaCommand, ParseError> {
    if command.starts_with(".exit") {
        Ok(MetaCommand::Exit)
    } else {
        Err(ParseError::Unrecognized)
    }
}

enum ParseError {
    Unrecognized,
    InvalidSyntax,
    StringTooLong,
    NegativeID,
}

enum Statement {
    Insert(Row),
    Select,
}

fn prepare_statement(input: &str) -> Result<Statement, ParseError> {
    if input.starts_with("insert") {
        let mut tokens = input.split_whitespace();
        let _ = tokens.next(); // skip "insert"
        match (tokens.next(), tokens.next(), tokens.next()) {
            (Some(id_str), Some(username), Some(email)) => {
                let id = id_str
                    .parse::<i32>()
                    .map_err(|_| ParseError::InvalidSyntax)?;
                if id < 0 {
                    return Err(ParseError::NegativeID);
                }
                let (uname_bytes, email_bytes) = (username.as_bytes(), email.as_bytes());
                if uname_bytes.len() > MAX_UNAME_LENGTH || email_bytes.len() > MAX_EMAIL_LENGTH {
                    return Err(ParseError::StringTooLong);
                }
                Ok(Statement::Insert(
                    Row::new(id as u32, uname_bytes, email_bytes),
                ))
            }
            _ => Err(ParseError::InvalidSyntax),
        }
    } else if input.starts_with("select") {
        Ok(Statement::Select)
    } else {
        Err(ParseError::Unrecognized)
    }
}

enum ExecuteError {
    TableFull,
}

fn execute_insert(row: &Row, table: &mut Table) -> Result<(), ExecuteError> {
    let num_rows = table.num_rows as usize;
    table.insert_row(row, num_rows)?;
    table.num_rows += 1;
    Ok(())
}

fn execute_select(table: &mut Table) -> Result<(), ExecuteError> {
    for i in 0..table.num_rows {
        println!("{}", table.row_slot(i as usize));
    }
    Ok(())
}

fn execute_statement(statement: Statement, table: &mut Table) -> Result<(), ExecuteError> {
    match statement {
        Statement::Insert(row) => execute_insert(&row, table),
        Statement::Select => execute_select(table),
    }
}

fn print_prompt() {
    print!("db > ");
    io::stdout().flush().unwrap();
}

fn db_open(filename: &str) -> Result<Table, io::Error> {
    let pager = Pager::open(filename)?;
    let table = Table::new(pager);
    Ok(table)
}

fn db_close(table: &mut Table) -> Result<(), io::Error> {
    let full_pages = table.num_rows as usize / ROWS_PER_PAGE;
    for i in 0..full_pages {
        table.pager.flush_page(i)?;
    }
    let remaining_rows = table.num_rows as usize % ROWS_PER_PAGE;
    if remaining_rows > 0 {
        table.pager.flush_partial(full_pages, remaining_rows)?;
    }
    Ok(())
}

fn main() {
    let mut args = env::args();
    let filename = match (args.next(), args.next()) {
        (_, None) => {
            println!("Must supply a database filename.");
            std::process::exit(1);
        }
        (_, Some(filename)) => filename,
    };

    let mut table = match db_open(&filename) {
        Ok(t) => t,
        Err(e) => {
            println!("Could not open file {}: {}", filename, e);
            std::process::exit(1);
        }
    };

    let stdin = io::stdin();
    let lines = stdin.lock().lines();

    print_prompt();
    for line in lines {
        if let Ok(input) = line {
            if let Some('.') = input.chars().next() {
                match do_meta_command(&input) {
                    Ok(MetaCommand::Exit) => break,
                    Err(ParseError::Unrecognized) => println!("Unrecognized command '{}'", input),
                    _ => {}
                }
            }

            match prepare_statement(&input) {
                Ok(statement) => match execute_statement(statement, &mut table) {
                    Ok(()) => println!("Executed."),
                    Err(ExecuteError::TableFull) => println!("Error: Table full."),
                },
                Err(ParseError::Unrecognized) => {
                    println!("Unrecognized keyword at start of {}", input)
                }
                Err(ParseError::NegativeID) => println!("ID must be positive."),
                Err(ParseError::StringTooLong) => println!("String is too long."),
                Err(ParseError::InvalidSyntax) => {
                    println!("Syntax error: could not parse statement.")
                }
            }
        }
        print_prompt();
    }
    if let Err(e) = db_close(&mut table) {
        println!("Could not close db {}: {}", filename, e);
    }
}
<commit_msg>Part 6<commit_after>#![feature(const_size_of)]
extern crate byteorder;

use std::io;
use std::io::prelude::*;
use std::cmp;
use std::fmt;
use std::str;
use std::env;
use std::fs::{File, OpenOptions};
use byteorder::{ReadBytesExt, WriteBytesExt, LE};

const MAX_UNAME_LENGTH: usize = 32;
const MAX_EMAIL_LENGTH: usize = 256;

#[derive(Copy)]
struct Row {
    id: u32,
    username_len: u32,
    username: [u8; MAX_UNAME_LENGTH],
    email_len: u32,
    email: [u8; MAX_EMAIL_LENGTH],
}
const ROW_SIZE: usize = std::mem::size_of::<Row>();

#[derive(Debug)]
enum PagerError {
    OutOfBounds { page_num: usize, max_pages: usize },
    CouldNotRead,
}

struct Cursor<'a> {
    table: &'a mut Table,
    row_num: u32,
    end_of_table: bool,
}

impl<'a> Cursor<'a> {
    fn get_value(&mut self) -> &Row {
        let row_num = self.row_num as usize;
        let (page_num, row_offset) = (row_num / ROWS_PER_PAGE, row_num % ROWS_PER_PAGE);
        let page = self.table.pager.get_page(page_num).unwrap();
        page.get_row(row_offset)
    }

    fn set_value(&mut self, val: &Row) {
        let row_num = self.row_num as usize;
        let (page_num, row_offset) = (row_num / ROWS_PER_PAGE, row_num % ROWS_PER_PAGE);
        let page = self.table.pager.get_page_mut(page_num).unwrap();
        page.set_row(row_offset, val);
    }

    fn advance(&mut self) {
        self.row_num += 1;
        if self.row_num >= self.table.num_rows {
            self.end_of_table = true;
        }
    }
}

struct Pager {
    // f: io::BufReader<File>,
    fd: File,
    file_size: u64,
    pages: [Option<Box<Page>>; TABLE_MAX_PAGES],
}

impl Pager {
    fn new(f: File, file_size: u64) -> Self {
        Pager {
            fd: f,
            file_size,
            pages: {
                let mut array: [Option<Box<Page>>; 100] = unsafe { std::mem::uninitialized() };
                for i in array.iter_mut() {
                    unsafe {
                        ::std::ptr::write(i, None);
                    }
                }
                array
            },
        }
    }

    fn open(filename: &str) -> Result<Box<Self>, io::Error> {
        let mut f = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(filename)?;
        let file_size = f.seek(io::SeekFrom::End(0))
            .expect("Could not read to end of file");
        let pager = Box::new(Pager::new(f, file_size));
        Ok(pager)
    }

    fn read_into_row<R: Read>(&self, reader: &mut R, row: &mut Row) -> io::Result<()> {
        row.id = reader.read_u32::<LE>()?;
        row.username_len = reader.read_u32::<LE>()?;
        reader.read(&mut row.username)?;
        row.email_len = reader.read_u32::<LE>()?;
        reader.read(&mut row.email)?;
        Ok(())
    }

    fn get_page<'a>(&'a mut self, page_num: usize) -> Result<&'a Page, PagerError> {
        if page_num > TABLE_MAX_PAGES {
            Err(PagerError::OutOfBounds {
                page_num,
                max_pages: TABLE_MAX_PAGES,
            })
        } else {
            match self.pages[page_num] {
                Some(ref page) => Ok(page),
                None => {
                    let mut new_page = self.allocate_page(page_num as u64).unwrap();
                    // .map_err(|_| PagerError::CouldNotRead)?;
                    self.pages[page_num] = Some(new_page);
                    Ok(self.pages[page_num].as_ref().unwrap())
                }
            }
        }
    }

    fn allocate_page(&mut self, page_num: u64) -> io::Result<Box<Page>> {
        let mut new_page = Box::new(Page::new());
        let num_pages = self.file_size / PAGE_SIZE + ((self.file_size % PAGE_SIZE != 0) as u64);

        if (page_num as u64) < num_pages {
            self.fd
                .seek(io::SeekFrom::Start(page_num as u64 * PAGE_SIZE))?;
            let mut row_bytes = [0u8; ROW_SIZE];
            for row in new_page.rows.iter_mut() {
                match self.fd.read(&mut row_bytes) {
                    Ok(ROW_SIZE) => self.read_into_row(&mut row_bytes.as_ref(), row)?,
                    Ok(0) => break,
                    Ok(_) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof)),
                    Err(e) => return Err(e),
                }
            }
        }
        Ok(new_page)
    }

    fn get_page_mut<'a>(&'a mut self, page_num: usize) -> Result<&'a mut Page, PagerError> {
        match self.pages[page_num] {
            Some(ref mut page) => Ok(page),
            None => {
                let mut new_page = self.allocate_page(page_num as u64)
                    .map_err(|_| PagerError::CouldNotRead)?;
                self.pages[page_num] = Some(new_page);
                Ok(self.pages[page_num].as_mut().unwrap())
                // match self.pages[page_num as usize] {
                //     Some(ref mut page) => page.borrow_mut(),
                //     _ => unreachable!(),
                // }
            }
        }
    }

    // FIXME get rid of this
    fn flush_partial(&mut self, page_num: usize, rows: usize) -> Result<(), io::Error> {
        if let Some(ref page) = self.pages[page_num] {
            let mut out = io::BufWriter::with_capacity(ROW_SIZE * rows, &mut self.fd);
            out.seek(io::SeekFrom::Start(page_num as u64 * PAGE_SIZE))
                .unwrap();
            for r in page.rows[..rows].iter() {
                out.write_u32::<LE>(r.id)?;
                out.write_u32::<LE>(r.username_len)?;
                out.write(&r.username)?;
                out.write_u32::<LE>(r.email_len)?;
                out.write(&r.email)?;
            }
        }
        Ok(())
    }

    fn flush_page(&mut self, page_num: usize) -> Result<(), io::Error> {
        let mut out = io::BufWriter::with_capacity(PAGE_SIZE as usize, &mut self.fd);
        if let Some(ref page) = self.pages[page_num] {
            out.seek(io::SeekFrom::Start(page_num as u64 * PAGE_SIZE))
                .unwrap();
            for r in page.rows.iter() {
                out.write_u32::<LE>(r.id)?;
                out.write_u32::<LE>(r.username_len)?;
                out.write(&r.username)?;
                out.write_u32::<LE>(r.email_len)?;
                out.write(&r.email)?;
            }
        }
        Ok(())
    }
}

impl Row {
    fn new(id: u32, username: &[u8], email: &[u8]) -> Self {
        let username_len = cmp::min(MAX_UNAME_LENGTH, username.len());
        let email_len = cmp::min(MAX_EMAIL_LENGTH, email.len());
        let mut row = Row {
            id: id,
            username_len: username_len as u32,
            username: [0; MAX_UNAME_LENGTH],
            email_len: email_len as u32,
            email: [0; MAX_EMAIL_LENGTH],
        };
        row.username[..username_len].copy_from_slice(&username[..username_len]);
        row.email[..email_len].copy_from_slice(&email[..email_len]);
        row
    }
}

impl Default for Row {
    fn default() -> Self {
        Row {
            id: 0,
            username_len: 0,
            username: [0; MAX_UNAME_LENGTH],
            email_len: 0,
            email: [0; MAX_EMAIL_LENGTH],
        }
    }
}

// Hack because not all fixed-size array struct members are `Clone`
impl Clone for Row {
    fn clone(&self) -> Self {
        *self
    }
}

impl fmt::Display for Row {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match (
            str::from_utf8(&self.username[..self.username_len as usize]),
            str::from_utf8(&self.email[..self.email_len as usize]),
        ) {
            (Ok(username), Ok(email)) => write!(f, "({}, {}, {})", self.id, username, email),
            _ => Err(fmt::Error),
        }
    }
}

const ROWS_PER_PAGE: usize = 4096 / std::mem::size_of::<Row>();
const TABLE_MAX_PAGES: usize = 100;
const TABLE_MAX_ROWS: u32 = (ROWS_PER_PAGE * TABLE_MAX_PAGES) as u32;
const PAGE_SIZE: u64 = std::mem::size_of::<Page>() as u64;

#[derive(Copy, Clone)]
struct Page {
    rows: [Row; ROWS_PER_PAGE],
}

impl Page {
    fn new() -> Self {
        Page {
            rows: [Default::default(); ROWS_PER_PAGE],
        }
    }

    fn get_row<'a>(&'a self, row_num: usize) -> &'a Row {
        &self.rows[row_num]
    }

    fn set_row<'a>(&'a mut self, row_num: usize, row: &Row) {
        self.rows[row_num] = *row;
    }
}

struct Table {
    pager: Box<Pager>,
    num_rows: u32,
}

impl<'a> Table {
    fn new(pager: Box<Pager>) -> Self {
        Table {
            num_rows: (pager.file_size / ROW_SIZE as u64) as u32,
            pager,
        }
    }

    fn start(&mut self) -> Box<Cursor> {
        let end_of_table = self.num_rows == 0;
        Box::new(Cursor {
            table: self,
            row_num: 0,
            end_of_table,
        })
    }

    fn end(&mut self) -> Box<Cursor> {
        let row_num = self.num_rows;
        Box::new(Cursor {
            table: self,
            row_num,
            end_of_table: true,
        })
    }
}

enum MetaCommand {
    Exit,
}

fn do_meta_command(command: &str) -> Result<MetaCommand, ParseError> {
    if command.starts_with(".exit") {
        Ok(MetaCommand::Exit)
    } else {
        Err(ParseError::Unrecognized)
    }
}

enum ParseError {
    Unrecognized,
    InvalidSyntax,
    StringTooLong,
    NegativeID,
}

enum Statement {
    Insert(Row),
    Select,
}

fn prepare_statement(input: &str) -> Result<Statement, ParseError> {
    if input.starts_with("insert") {
        let mut tokens = input.split_whitespace();
        let _ = tokens.next(); // skip "insert"
        match (tokens.next(), tokens.next(), tokens.next()) {
            (Some(id_str), Some(username), Some(email)) => {
                let id = id_str
                    .parse::<i32>()
                    .map_err(|_| ParseError::InvalidSyntax)?;
                if id < 0 {
                    return Err(ParseError::NegativeID);
                }
                let (uname_bytes, email_bytes) = (username.as_bytes(), email.as_bytes());
                if uname_bytes.len() > MAX_UNAME_LENGTH || email_bytes.len() > MAX_EMAIL_LENGTH {
                    return Err(ParseError::StringTooLong);
                }
                Ok(Statement::Insert(
                    Row::new(id as u32, uname_bytes, email_bytes),
                ))
            }
            _ => Err(ParseError::InvalidSyntax),
        }
    } else if input.starts_with("select") {
        Ok(Statement::Select)
    } else {
        Err(ParseError::Unrecognized)
    }
}

enum ExecuteError {
    TableFull,
}

fn execute_insert(row: &Row, table: &mut Table) -> Result<(), ExecuteError> {
    let num_rows = table.num_rows;
    if num_rows >= TABLE_MAX_ROWS {
        return Err(ExecuteError::TableFull);
    }
    {
        let mut cursor = table.end();
        cursor.set_value(row);
    }
    table.num_rows += 1;
    Ok(())
}

fn execute_select(table: &mut Table) -> Result<(), ExecuteError> {
    let mut cursor = table.start();
    while !cursor.end_of_table {
        println!("{}", cursor.get_value());
        cursor.advance();
    }
    Ok(())
}

fn execute_statement(statement: Statement, table: &mut Table) -> Result<(), ExecuteError> {
    match statement {
        Statement::Insert(row) => execute_insert(&row, table),
        Statement::Select => execute_select(table),
    }
}

fn print_prompt() {
    print!("db > ");
    io::stdout().flush().unwrap();
}

fn db_open(filename: &str) -> Result<Table, io::Error> {
    let pager = Pager::open(filename)?;
    let table = Table::new(pager);
    Ok(table)
}

fn db_close(table: &mut Table) -> Result<(), io::Error> {
    let full_pages = table.num_rows as usize / ROWS_PER_PAGE;
    for i in 0..full_pages {
        table.pager.flush_page(i)?;
    }
    let remaining_rows = table.num_rows as usize % ROWS_PER_PAGE;
    if remaining_rows > 0 {
        table.pager.flush_partial(full_pages, remaining_rows)?;
    }
    Ok(())
}

fn main() {
    let mut args = env::args();
    let filename = match (args.next(), args.next()) {
        (_, None) => {
            println!("Must supply a database filename.");
            std::process::exit(1);
        }
        (_, Some(filename)) => filename,
    };

    let mut table = match db_open(&filename) {
        Ok(t) => t,
        Err(e) => {
            println!("Could not open file {}: {}", filename, e);
            std::process::exit(1);
        }
    };

    let stdin = io::stdin();
    let lines = stdin.lock().lines();

    print_prompt();
    for line in lines {
        if let Ok(input) = line {
            if let Some('.') = input.chars().next() {
                match do_meta_command(&input) {
                    Ok(MetaCommand::Exit) => break,
                    Err(ParseError::Unrecognized) => println!("Unrecognized command '{}'", input),
                    _ => {}
                }
            }

            match prepare_statement(&input) {
                Ok(statement) => match execute_statement(statement, &mut table) {
                    Ok(()) => println!("Executed."),
                    Err(ExecuteError::TableFull) => println!("Error: Table full."),
                },
                Err(ParseError::Unrecognized) => {
                    println!("Unrecognized keyword at start of {}", input)
                }
                Err(ParseError::NegativeID) => println!("ID must be positive."),
                Err(ParseError::StringTooLong) => println!("String is too long."),
                Err(ParseError::InvalidSyntax) => {
                    println!("Syntax error: could not parse statement.")
                }
            }
        }
        print_prompt();
    }
    if let Err(e) = db_close(&mut table) {
        println!("Could not close db {}: {}", filename, e);
    }
}
<|endoftext|>


<commit_before>use std::env;
use std::io::prelude::*;
use std::io;
use std::fs::File;

// include output of rust-peg given grammar.rustpeg
mod parser {
    include!(concat!(env!("OUT_DIR"), "/grammar.rs"));
}

mod ast;
mod interpreter;
mod value;
mod operations;

#[derive(Debug)]
enum ProcessingError {
    ParseError(parser::ParseError),
    IoError(io::Error)
}

impl From<io::Error> for ProcessingError {
    fn from(from: io::Error) -> Self {
        ProcessingError::IoError(from)
    }
}

impl From<parser::ParseError> for ProcessingError {
    fn from(from: parser::ParseError) -> Self {
        ProcessingError::ParseError(from)
    }
}

fn main() {
    let args: Vec<_> = env::args().collect();
    if args.len() < 3 || (args[1] != "run" && args[1] != "parse") {
        println!("usage: balloon run|parse FILE");
        return;
    }
    let result = parse_file(&args[2]).and_then(|ast| {
        if args[1] == "parse" {
            println!("{:#?}", ast);
            Ok(())
        } else {
            interpreter::interpret_program(&ast);
            Ok(())
        }
    });
    if let Err(err) = result {
        print!("Error: ");
        println!("{:?}", err);
    }
}

fn parse_file(name: &String) -> Result<Vec<ast::Statement>, ProcessingError> {
    let mut input_file = File::open(name)?;
    let mut input = String::new();
    input_file.read_to_string(&mut input)?;
    if !input.ends_with("\n") {
        input.push('\n');
    }
    let x = parser::program(&input);
    Ok(x?)
}
<commit_msg>Prettier printing for parse errors<commit_after>use std::env;
use std::io::prelude::*;
use std::io;
use std::fs::File;

// include output of rust-peg given grammar.rustpeg
mod parser {
    include!(concat!(env!("OUT_DIR"), "/grammar.rs"));
}

mod ast;
mod interpreter;
mod value;
mod operations;

#[derive(Debug)]
enum ProcessingError {
    ParseError(parser::ParseError),
    IoError(io::Error)
}

impl From<io::Error> for ProcessingError {
    fn from(from: io::Error) -> Self {
        ProcessingError::IoError(from)
    }
}

impl From<parser::ParseError> for ProcessingError {
    fn from(from: parser::ParseError) -> Self {
        ProcessingError::ParseError(from)
    }
}

fn main() {
    let args: Vec<_> = env::args().collect();
    if args.len() < 3 || (args[1] != "run" && args[1] != "parse") {
        println!("usage: balloon run|parse FILE");
        return;
    }
    let file_name = &args[2];
    let result = parse_file(file_name).and_then(|ast| {
        if args[1] == "parse" {
            println!("{:#?}", ast);
            Ok(())
        } else {
            interpreter::interpret_program(&ast);
            Ok(())
        }
    });
    if let Err(err) = result {
        match err {
            ProcessingError::ParseError(parse_error) => {
                let (line, column) = (parse_error.line, parse_error.column);
                // TODO: find cleaner, but still expressive way
                let line_content = io::BufReader::new(File::open(file_name).unwrap())
                    .lines().nth(line - 1)
                    .unwrap().unwrap();

                println!("{}: parse error: line {}, column {}: expected one of {:?}",
                    file_name, line, column, parse_error.expected);
                println!("{}", line_content);
                let mut pointer_string = String::from_utf8(vec![b' '; column]).unwrap();
                pointer_string.push('^');
                println!("{}", pointer_string);
            },
            ProcessingError::IoError(io_error) => {
                match io_error.kind() {
                    io::ErrorKind::NotFound => println!("{}", io_error),
                    e => println!("An error occurred.\n{:?}", e),
                };
            },
        };
    }
}

fn parse_file(name: &String) -> Result<Vec<ast::Statement>, ProcessingError> {
    let mut input_file = File::open(name)?;
    let mut input = String::new();
    input_file.read_to_string(&mut input)?;
    if !input.ends_with("\n") {
        input.push('\n');
    }
    let x = parser::program(&input);
    Ok(x?)
}
<|endoftext|>


<commit_before>extern crate alpm;
#[macro_use]
extern crate clap;
extern crate curl;
extern crate env_logger;
extern crate itertools;
#[macro_use]
extern crate log;
extern crate serde_json;

use clap::App;
use curl::easy::Easy;
use itertools::Itertools;
use serde_json::Value;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::collections::btree_map::Entry::{Occupied, Vacant};
use std::default::Default;
use std::process::exit;
use std::str;

mod avg;
mod enums;

struct Options {
    format: Option<String>,
    quiet: u64,
    upgradable_only: bool,
}

impl Default for Options {
    fn default() -> Options {
        Options {
            format: None,
            quiet: 0,
            upgradable_only: false,
        }
    }
}

fn main() {
    env_logger::init();

    let yaml = load_yaml!("cli.yml");
    let args = App::from_yaml(yaml).get_matches();

    let options = Options {
        format: {
            match args.value_of("format") {
                Some(f) => Some(f.to_string()),
                None => None,
            }
        },
        quiet: args.occurrences_of("quiet"),
        upgradable_only: args.is_present("upgradable"),
    };

    let mut avgs = String::new();
    {
        info!("Downloading AVGs...");
        let avgs_url = "https://security.archlinux.org/issues/all.json";

        let mut easy = Easy::new();
        easy.url(avgs_url).expect("curl::Easy::url failed");
        let mut transfer = easy.transfer();
        transfer
            .write_function(|data| {
                avgs.push_str(str::from_utf8(data).expect("str conversion failed"));
                Ok(data.len())
            })
            .expect("write_function failed");
        match transfer.perform() {
            Ok(_) => {}
            Err(_) => {
                println!("Cannot fetch data, please check your network connection!");
                exit(1)
            }
        };
    }

    let pacman = match args.value_of("dbpath") {
        Some(path) => {
            alpm::Alpm::with_dbpath(path.to_string()).expect("alpm::Alpm::with_dbpath failed")
        }
        None => alpm::Alpm::new().expect("alpm::Alpm::new failed"),
    };

    let mut cves: BTreeMap<String, Vec<_>> = BTreeMap::new();
    {
        let json: Value = serde_json::from_str(&avgs).expect("serde_json::from_str failed");

        for avg in json.as_array().expect("Value::as_array failed") {
            let packages = avg["packages"]
                .as_array()
                .expect("Value::as_array failed")
                .iter()
                .map(|s| s.as_str().expect("Value::as_str failed").to_string())
                .collect::<Vec<_>>();

            if !package_is_installed(&pacman, &packages) {
                continue;
            }

            let info = to_avg(avg);

            if info.status != enums::Status::NotAffected {
                for p in packages {
                    match cves.entry(p) {
                        Occupied(c) => c.into_mut(),
                        Vacant(c) => c.insert(vec![]),
                    }.push(info.clone());
                }
            }
        }
    }

    let mut affected_avgs: BTreeMap<String, Vec<_>> = BTreeMap::new();
    for (pkg, avgs) in cves {
        for avg in &avgs {
            if system_is_affected(&pacman, &pkg, avg) {
                match affected_avgs.entry(pkg.clone()) {
                    Occupied(c) => c.into_mut(),
                    Vacant(c) => c.insert(vec![]),
                }.push(avg.clone());
            }
        }
    }

    let merged = merge_avgs(&pacman, &affected_avgs);
    print_avgs(&options, &merged);
}

/// Converts a JSON to an `avg::AVG`
fn to_avg(data: &Value) -> avg::AVG {
    avg::AVG {
        issues: data["issues"]
            .as_array()
            .expect("Value::as_array failed")
            .iter()
            .map(|s| s.as_str().expect("Value::as_str failed").to_string())
            .collect(),
        fixed: match data["fixed"].as_str() {
            Some(s) => Some(s.to_string()),
            None => None,
        },
        severity: data["severity"]
            .as_str()
            .expect("Value::as_str failed")
            .to_string()
            .parse::<enums::Severity>()
            .expect("parse::<Severity> failed"),
        status: data["status"]
            .as_str()
            .expect("Value::as_str failed")
            .to_string()
            .parse::<enums::Status>()
            .expect("parse::<Status> failed"),
    }
}

#[test]
fn test_to_avg() {
    let json: Value = serde_json::from_str(
        "{\"issues\": [\"CVE-1\", \"CVE-2\"], \"fixed\": \"1.0\", \
                               \"severity\": \"High\", \"status\": \"Not affected\"}",
    ).expect("serde_json::from_str failed");

    let avg1 = to_avg(&json);
    assert_eq!(2, avg1.issues.len());
    assert_eq!(Some("1.0".to_string()), avg1.fixed);
    assert_eq!(enums::Severity::High, avg1.severity);
    assert_eq!(enums::Status::NotAffected, avg1.status);

    let json: Value = serde_json::from_str(
        "{\"issues\": [\"CVE-1\"], \"fixed\": null, \
                               \"severity\": \"Low\", \"status\": \"Vulnerable\"}",
    ).expect("serde_json::from_str failed");

    let avg2 = to_avg(&json);
    assert_eq!(1, avg2.issues.len());
    assert_eq!(None, avg2.fixed);
    assert_eq!(enums::Severity::Low, avg2.severity);
    assert_eq!(enums::Status::Vulnerable, avg2.status);
}

/// Given a package and an `avg::AVG`, returns true if the system is affected
fn system_is_affected(pacman: &alpm::Alpm, pkg: &str, avg: &avg::AVG) -> bool {
    match pacman.query_package_version(pkg.clone()) {
        Ok(v) => {
            info!("Found installed version {} for package {}", v, pkg);
            match avg.fixed {
                Some(ref version) => {
                    info!("Comparing with fixed version {}", version);
                    let cmp = pacman.vercmp(v.clone(), version.clone()).expect(
                        "Alpm::vercmp failed",
                    );
                    if let Ordering::Less = cmp {
                        return true;
                    }
                }
                None => return true,
            };
        }
        Err(_) => debug!("Package {} not installed", pkg),
    }

    false
}

#[test]
fn test_system_is_affected() {
    let pacman = alpm::Alpm::new().expect("Alpm::new failed");

    let avg1 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("1.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Unknown,
    };

    assert_eq!(
        false,
        system_is_affected(&pacman, &"pacman".to_string(), &avg1)
    );

    let avg2 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("7.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Unknown,
    };

    assert!(system_is_affected(&pacman, &"pacman".to_string(), &avg2));
}

/// Given a list of package names, returns true when at least one is installed
fn package_is_installed(pacman: &alpm::Alpm, packages: &[String]) -> bool {
    for pkg in packages {
        match pacman.query_package_version(pkg.as_str()) {
            Ok(_) => {
                info!("Package {} is installed", pkg);
                return true;
            }
            Err(_) => debug!("Package {} not installed", pkg),
        }
    }
    false
}

#[test]
fn test_package_is_installed() {
    let pacman = alpm::Alpm::new().expect("Alpm::new failed");

    let packages = vec!["pacman".to_string(), "pac".to_string()];
    assert!(package_is_installed(&pacman, &packages));

    let packages = vec!["pac".to_string()];
    assert_eq!(false, package_is_installed(&pacman, &packages));
}

/// Merge a list of `avg::AVG` into a single `avg::AVG` using major version as version
fn merge_avgs(
    pacman: &alpm::Alpm,
    cves: &BTreeMap<String, Vec<avg::AVG>>,
) -> BTreeMap<String, avg::AVG> {
    let mut avgs: BTreeMap<String, avg::AVG> = BTreeMap::new();
    for (pkg, list) in cves.iter() {
        let mut avg_issues = vec![];
        let mut avg_fixed: Option<String> = None;
        let mut avg_severity = enums::Severity::Unknown;
        let mut avg_status = enums::Status::Unknown;

        for a in list.iter() {
            avg_issues.append(&mut a.issues.clone());

            match avg_fixed.clone() {
                Some(ref version) => {
                    if let Some(ref v) = a.fixed {
                        let cmp = pacman.vercmp(version.to_string(), v.to_string()).expect(
                            "Alpm::vercmp failed",
                        );
                        if let Ordering::Greater = cmp {
                            avg_fixed = a.fixed.clone();
                        }
                    }
                }
                None => avg_fixed = a.fixed.clone(),
            }

            if a.severity > avg_severity {
                avg_severity = a.severity;
            }

            if a.status > avg_status {
                avg_status = a.status;
            }
        }

        let avg = avg::AVG {
            issues: avg_issues,
            fixed: avg_fixed,
            severity: avg_severity,
            status: avg_status,
        };
        avgs.insert(pkg.to_string(), avg);
    }

    avgs
}

#[test]
fn test_merge_avgs() {
    let mut avgs: BTreeMap<String, Vec<_>> = BTreeMap::new();

    let avg1 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("1.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Fixed,
    };

    let avg2 = avg::AVG {
        issues: vec!["CVE-4".to_string(), "CVE-10".to_string()],
        fixed: Some("0.9.8".to_string()),
        severity: enums::Severity::High,
        status: enums::Status::Testing,
    };

    assert!(enums::Severity::Critical > enums::Severity::High);

    avgs.insert("package".to_string(), vec![avg1.clone(), avg2.clone()]);

    avgs.insert("package2".to_string(), vec![avg1, avg2]);

    let pacman = alpm::Alpm::new().expect("Alpm::new failed");
    let merged = merge_avgs(&pacman, &avgs);

    assert_eq!(2, merged.len());
    assert_eq!(
        4,
        merged
            .get(&"package".to_string())
            .expect("'package' key not found")
            .issues
            .len()
    );
    assert_eq!(
        enums::Severity::High,
        merged
            .get(&"package".to_string())
            .expect("'package' key not found")
            .severity
    );
    assert_eq!(
        enums::Status::Testing,
        merged
            .get(&"package".to_string())
            .expect("'package' key not found")
            .status
    );
}

/// Print a list of `avg::AVG`
fn print_avgs(options: &Options, avgs: &BTreeMap<String, avg::AVG>) {
    for (pkg, avg) in avgs {
        match avg.fixed {
            Some(ref v) if avg.status != enums::Status::Vulnerable => {
                if options.quiet >= 2 {
                    println!("{}", pkg);
                } else if options.quiet == 1 {
                    println!("{}>={}", pkg, v);
                } else {
                    match options.format {
                        Some(ref f) => {
                            println!(
                                "{}",
                                f.replace("%n", pkg.as_str()).replace(
                                    "%c",
                                    avg.issues
                                        .iter()
                                        .join(",")
                                        .as_str(),
                                )
                            )
                        }
                        None => {
                            let msg = format!(
                                "Package {} is affected by {}. {}!",
                                pkg,
                                avg.issues.join(", "),
                                avg.severity
                            );

                            if avg.status == enums::Status::Testing {
                                println!("{}. Update to {} from testing repos!", msg, v)
                            } else if avg.status == enums::Status::Fixed {
                                println!("{}. Update to {}!", msg, v)
                            } else {
                                println!("{}", msg)
                            }
                        }
                    }
                }
            }
            _ => {
                if !options.upgradable_only {
                    if options.quiet > 0 {
                        println!("{}", pkg);
                    } else {
                        match options.format {
                            Some(ref f) => {
                                println!(
                                    "{}",
                                    f.replace("%n", pkg.as_str()).replace(
                                        "%c",
                                        avg.issues
                                            .iter()
                                            .join(",")
                                            .as_str(),
                                    )
                                )
                            }
                            None => {
                                println!(
                                    "Package {} is affected by {}. {}!",
                                    pkg,
                                    avg.issues.join(", "),
                                    avg.severity
                                );
                            }
                        }
                    }
                }
            }
        }
    }
}
<commit_msg>Rustfmt<commit_after>extern crate alpm;
#[macro_use]
extern crate clap;
extern crate curl;
extern crate env_logger;
extern crate itertools;
#[macro_use]
extern crate log;
extern crate serde_json;

use clap::App;
use curl::easy::Easy;
use itertools::Itertools;
use serde_json::Value;
use std::cmp::Ordering;
use std::collections::btree_map::Entry::{Occupied, Vacant};
use std::collections::BTreeMap;
use std::default::Default;
use std::process::exit;
use std::str;

mod avg;
mod enums;

struct Options {
    format: Option<String>,
    quiet: u64,
    upgradable_only: bool,
}

impl Default for Options {
    fn default() -> Options {
        Options {
            format: None,
            quiet: 0,
            upgradable_only: false,
        }
    }
}

fn main() {
    env_logger::init();

    let yaml = load_yaml!("cli.yml");
    let args = App::from_yaml(yaml).get_matches();

    let options = Options {
        format: {
            match args.value_of("format") {
                Some(f) => Some(f.to_string()),
                None => None,
            }
        },
        quiet: args.occurrences_of("quiet"),
        upgradable_only: args.is_present("upgradable"),
    };

    let mut avgs = String::new();
    {
        info!("Downloading AVGs...");
        let avgs_url = "https://security.archlinux.org/issues/all.json";

        let mut easy = Easy::new();
        easy.url(avgs_url).expect("curl::Easy::url failed");
        let mut transfer = easy.transfer();
        transfer
            .write_function(|data| {
                avgs.push_str(str::from_utf8(data).expect("str conversion failed"));
                Ok(data.len())
            })
            .expect("write_function failed");
        match transfer.perform() {
            Ok(_) => {}
            Err(_) => {
                println!("Cannot fetch data, please check your network connection!");
                exit(1)
            }
        };
    }

    let pacman = match args.value_of("dbpath") {
        Some(path) => {
            alpm::Alpm::with_dbpath(path.to_string()).expect("alpm::Alpm::with_dbpath failed")
        }
        None => alpm::Alpm::new().expect("alpm::Alpm::new failed"),
    };

    let mut cves: BTreeMap<String, Vec<_>> = BTreeMap::new();
    {
        let json: Value = serde_json::from_str(&avgs).expect("serde_json::from_str failed");

        for avg in json.as_array().expect("Value::as_array failed") {
            let packages = avg["packages"]
                .as_array()
                .expect("Value::as_array failed")
                .iter()
                .map(|s| s.as_str().expect("Value::as_str failed").to_string())
                .collect::<Vec<_>>();

            if !package_is_installed(&pacman, &packages) {
                continue;
            }

            let info = to_avg(avg);

            if info.status != enums::Status::NotAffected {
                for p in packages {
                    match cves.entry(p) {
                        Occupied(c) => c.into_mut(),
                        Vacant(c) => c.insert(vec![]),
                    }
                    .push(info.clone());
                }
            }
        }
    }

    let mut affected_avgs: BTreeMap<String, Vec<_>> = BTreeMap::new();
    for (pkg, avgs) in cves {
        for avg in &avgs {
            if system_is_affected(&pacman, &pkg, avg) {
                match affected_avgs.entry(pkg.clone()) {
                    Occupied(c) => c.into_mut(),
                    Vacant(c) => c.insert(vec![]),
                }
                .push(avg.clone());
            }
        }
    }

    let merged = merge_avgs(&pacman, &affected_avgs);
    print_avgs(&options, &merged);
}

/// Converts a JSON to an `avg::AVG`
fn to_avg(data: &Value) -> avg::AVG {
    avg::AVG {
        issues: data["issues"]
            .as_array()
            .expect("Value::as_array failed")
            .iter()
            .map(|s| s.as_str().expect("Value::as_str failed").to_string())
            .collect(),
        fixed: match data["fixed"].as_str() {
            Some(s) => Some(s.to_string()),
            None => None,
        },
        severity: data["severity"]
            .as_str()
            .expect("Value::as_str failed")
            .to_string()
            .parse::<enums::Severity>()
            .expect("parse::<Severity> failed"),
        status: data["status"]
            .as_str()
            .expect("Value::as_str failed")
            .to_string()
            .parse::<enums::Status>()
            .expect("parse::<Status> failed"),
    }
}

#[test]
fn test_to_avg() {
    let json: Value = serde_json::from_str(
        "{\"issues\": [\"CVE-1\", \"CVE-2\"], \"fixed\": \"1.0\", \
         \"severity\": \"High\", \"status\": \"Not affected\"}",
    )
    .expect("serde_json::from_str failed");

    let avg1 = to_avg(&json);
    assert_eq!(2, avg1.issues.len());
    assert_eq!(Some("1.0".to_string()), avg1.fixed);
    assert_eq!(enums::Severity::High, avg1.severity);
    assert_eq!(enums::Status::NotAffected, avg1.status);

    let json: Value = serde_json::from_str(
        "{\"issues\": [\"CVE-1\"], \"fixed\": null, \
         \"severity\": \"Low\", \"status\": \"Vulnerable\"}",
    )
    .expect("serde_json::from_str failed");

    let avg2 = to_avg(&json);
    assert_eq!(1, avg2.issues.len());
    assert_eq!(None, avg2.fixed);
    assert_eq!(enums::Severity::Low, avg2.severity);
    assert_eq!(enums::Status::Vulnerable, avg2.status);
}

/// Given a package and an `avg::AVG`, returns true if the system is affected
fn system_is_affected(pacman: &alpm::Alpm, pkg: &str, avg: &avg::AVG) -> bool {
    match pacman.query_package_version(pkg.clone()) {
        Ok(v) => {
            info!("Found installed version {} for package {}", v, pkg);
            match avg.fixed {
                Some(ref version) => {
                    info!("Comparing with fixed version {}", version);
                    let cmp = pacman
                        .vercmp(v.clone(), version.clone())
                        .expect("Alpm::vercmp failed");
                    if let Ordering::Less = cmp {
                        return true;
                    }
                }
                None => return true,
            };
        }
        Err(_) => debug!("Package {} not installed", pkg),
    }

    false
}

#[test]
fn test_system_is_affected() {
    let pacman = alpm::Alpm::new().expect("Alpm::new failed");

    let avg1 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("1.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Unknown,
    };

    assert_eq!(
        false,
        system_is_affected(&pacman, &"pacman".to_string(), &avg1)
    );

    let avg2 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("7.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Unknown,
    };

    assert!(system_is_affected(&pacman, &"pacman".to_string(), &avg2));
}

/// Given a list of package names, returns true when at least one is installed
fn package_is_installed(pacman: &alpm::Alpm, packages: &[String]) -> bool {
    for pkg in packages {
        match pacman.query_package_version(pkg.as_str()) {
            Ok(_) => {
                info!("Package {} is installed", pkg);
                return true;
            }
            Err(_) => debug!("Package {} not installed", pkg),
        }
    }
    false
}

#[test]
fn test_package_is_installed() {
    let pacman = alpm::Alpm::new().expect("Alpm::new failed");

    let packages = vec!["pacman".to_string(), "pac".to_string()];
    assert!(package_is_installed(&pacman, &packages));

    let packages = vec!["pac".to_string()];
    assert_eq!(false, package_is_installed(&pacman, &packages));
}

/// Merge a list of `avg::AVG` into a single `avg::AVG` using major version as version
fn merge_avgs(
    pacman: &alpm::Alpm,
    cves: &BTreeMap<String, Vec<avg::AVG>>,
) -> BTreeMap<String, avg::AVG> {
    let mut avgs: BTreeMap<String, avg::AVG> = BTreeMap::new();
    for (pkg, list) in cves.iter() {
        let mut avg_issues = vec![];
        let mut avg_fixed: Option<String> = None;
        let mut avg_severity = enums::Severity::Unknown;
        let mut avg_status = enums::Status::Unknown;

        for a in list.iter() {
            avg_issues.append(&mut a.issues.clone());

            match avg_fixed.clone() {
                Some(ref version) => {
                    if let Some(ref v) = a.fixed {
                        let cmp = pacman
                            .vercmp(version.to_string(), v.to_string())
                            .expect("Alpm::vercmp failed");
                        if let Ordering::Greater = cmp {
                            avg_fixed = a.fixed.clone();
                        }
                    }
                }
                None => avg_fixed = a.fixed.clone(),
            }

            if a.severity > avg_severity {
                avg_severity = a.severity;
            }

            if a.status > avg_status {
                avg_status = a.status;
            }
        }

        let avg = avg::AVG {
            issues: avg_issues,
            fixed: avg_fixed,
            severity: avg_severity,
            status: avg_status,
        };
        avgs.insert(pkg.to_string(), avg);
    }

    avgs
}

#[test]
fn test_merge_avgs() {
    let mut avgs: BTreeMap<String, Vec<_>> = BTreeMap::new();

    let avg1 = avg::AVG {
        issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
        fixed: Some("1.0.0".to_string()),
        severity: enums::Severity::Unknown,
        status: enums::Status::Fixed,
    };

    let avg2 = avg::AVG {
        issues: vec!["CVE-4".to_string(), "CVE-10".to_string()],
        fixed: Some("0.9.8".to_string()),
        severity: enums::Severity::High,
        status: enums::Status::Testing,
    };

    assert!(enums::Severity::Critical > enums::Severity::High);

    avgs.insert("package".to_string(), vec![avg1.clone(), avg2.clone()]);

    avgs.insert("package2".to_string(), vec![avg1, avg2]);

    let pacman = alpm::Alpm::new().expect("Alpm::new failed");
    let merged = merge_avgs(&pacman, &avgs);

    assert_eq!(2, merged.len());
    assert_eq!(
        4,
        merged
            .get(&"package".to_string())
            .expect("'package' key not found")
            .issues
            .len()
    );
    assert_eq!(
        enums::Severity::High,
        merged
            .get(&"package".to_string())
            .expect("'package' key not found")
            .severity
    );
    assert_eq!(
        enums::Status::Testing,
        merged
            .get(&"package".to_string())
            .expect("'package' key not found")
            .status
    );
}

/// Print a list of `avg::AVG`
fn print_avgs(options: &Options, avgs: &BTreeMap<String, avg::AVG>) {
    for (pkg, avg) in avgs {
        match avg.fixed {
            Some(ref v) if avg.status != enums::Status::Vulnerable => {
                if options.quiet >= 2 {
                    println!("{}", pkg);
                } else if options.quiet == 1 {
                    println!("{}>={}", pkg, v);
                } else {
                    match options.format {
                        Some(ref f) => println!(
                            "{}",
                            f.replace("%n", pkg.as_str())
                                .replace("%c", avg.issues.iter().join(",").as_str(),)
                        ),
                        None => {
                            let msg = format!(
                                "Package {} is affected by {}. {}!",
                                pkg,
                                avg.issues.join(", "),
                                avg.severity
                            );

                            if avg.status == enums::Status::Testing {
                                println!("{}. Update to {} from testing repos!", msg, v)
                            } else if avg.status == enums::Status::Fixed {
                                println!("{}. Update to {}!", msg, v)
                            } else {
                                println!("{}", msg)
                            }
                        }
                    }
                }
            }
            _ => {
                if !options.upgradable_only {
                    if options.quiet > 0 {
                        println!("{}", pkg);
                    } else {
                        match options.format {
                            Some(ref f) => println!(
                                "{}",
                                f.replace("%n", pkg.as_str())
                                    .replace("%c", avg.issues.iter().join(",").as_str(),)
                            ),
                            None => {
                                println!(
                                    "Package {} is affected by {}. {}!",
                                    pkg,
                                    avg.issues.join(", "),
                                    avg.severity
                                );
                            }
                        }
                    }
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate serde_derive;
extern crate curl;
extern crate rusqlite;

pub mod config;
mod inoreader;
mod telegram;
mod store;

//use std::thread;
use std::time::Duration;
use std::thread::sleep;

fn main() {
    // получаем список аргументов командной строки
    let cmd_args: Vec<_> = std::env::args().collect();

    let cfg = config::Config::new(&cmd_args[1]);
    let store = store::Store::connect(&cfg);

    let mut inoreader_client = inoreader::InoReaderClient::new(&cfg);
    let mut telegram_bot = telegram::TelegramBotClient::new(&cfg);

    loop {
        let new_chats = telegram_bot.get_chart_ids();
        let mut saved_charts = store.get_chart_ids();
        for c in new_chats {
            if !saved_charts.contains(&c) {
                store.add_chart(c);
                saved_charts.push(c);
            }
        }

        let unread_count = inoreader_client.get_unread_count();
        let news: Vec<inoreader::News> = inoreader_client.get_last_news(unread_count);

        for c in saved_charts {
            for n in news.clone() {
                telegram_bot.send_message(n, c);
            }
            //            thread::spawn(move || {}).join();
        }

        // ждем timeout в секундах для следующего обращения
        sleep(Duration::from_secs(cfg.timeout));
    }
}
<commit_msg>рассылка разнесена по отдельным потокам<commit_after>#[macro_use]
extern crate serde_derive;
extern crate curl;
extern crate rusqlite;

pub mod config;
mod inoreader;
mod telegram;
mod store;

use std::thread;
use std::process;
use std::time::Duration;
use std::thread::sleep;

fn main() {
    // получаем список аргументов командной строки
    let cmd_args: Vec<_> = std::env::args().collect();

    if cmd_args.len() < 2 {
        println!("Illegal parameter use: inobot <config_path>");
        process::exit(1);
    }
    let cfg = config::Config::new(&cmd_args[1]);
    let store = store::Store::connect(&cfg);

    let mut inoreader_client = inoreader::InoReaderClient::new(&cfg);
    let mut telegram_bot = telegram::TelegramBotClient::new(&cfg);

    loop {
        let new_chats = telegram_bot.get_chart_ids();
        let mut saved_charts = store.get_chart_ids();
        for c in new_chats {
            if !saved_charts.contains(&c) {
                store.add_chart(c);
                saved_charts.push(c);
            }
        }

        let unread_count = inoreader_client.get_unread_count();
        let news: Vec<inoreader::News> = inoreader_client.get_last_news(unread_count);

        for c in saved_charts {
            let sending_news = news.clone();
            let mut bot = telegram::TelegramBotClient::new(&cfg);
            let _ = thread::spawn(move || {
                for n in sending_news {
                    bot.send_message(n, c);
                }
            }).join();
        }

        // ждем timeout в секундах для следующего обращения
        sleep(Duration::from_secs(cfg.timeout));
    }
}
<|endoftext|>


<commit_before>#![allow(unknown_lints)]
#![warn(clippy)]

mod app;
mod backend;
mod core;
mod frontend;

#[macro_use]
extern crate log;
extern crate chrono;
extern crate csv;
extern crate log4rs;

#[macro_use]
extern crate bitflags;
extern crate bit_set;
extern crate cgmath;

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

extern crate wrapped2d;

#[macro_use]
extern crate gfx;
extern crate gl;
extern crate image;
extern crate gfx_device_gl;
extern crate glutin;
extern crate winit;

extern crate pitch_calc;
extern crate portaudio;
extern crate sample;

extern crate itertools;
extern crate num;
extern crate num_traits;
extern crate rand;

#[cfg(feature = "profiler")]
extern crate cpuprofiler;

#[macro_use]
extern crate enum_primitive;
extern crate conrod;

extern crate ctrlc;
extern crate getopts;

extern crate gilrs;

extern crate dirs;
extern crate rayon;
#[cfg(unix)]
extern crate thread_priority;

extern crate rustc_serialize as serialize;

fn main() {
	use log4rs::append::console::*;
	use log4rs::config::*;
	use std::env;
	let args = env::args_os().collect::<Vec<_>>();

	let config = Config::builder()
		.appender(Appender::builder().build("stdout".to_string(), Box::new(ConsoleAppender::builder().build())))
		.logger(Logger::builder().build("gfx_device_gl".to_string(), log::LevelFilter::Error))
		.logger(Logger::builder().build("rust_oids".to_string(), log::LevelFilter::Info))
		.build(
			Root::builder()
				.appender("stdout".to_string())
				.build(log::LevelFilter::Info),
		);
	log4rs::init_config(config.unwrap()).unwrap();

	#[cfg(feature = "profiler")]
	{
		cpuprofiler::PROFILER
			.lock()
			.unwrap()
			.start("./rust-oids.profile")
			.unwrap();
	}

	app::run(&args);

	#[cfg(feature = "profiler")]
	{
		cpuprofiler::PROFILER.lock().unwrap().stop().unwrap();
	}
}
<commit_msg>Clippy warnings; formatting<commit_after>#![allow(unknown_lints)]
#![feature(tool_lints)]
#![warn(clippy::all)]

mod app;
mod backend;
mod core;
mod frontend;

#[macro_use]
extern crate log;
extern crate chrono;
extern crate csv;
extern crate log4rs;

#[macro_use]
extern crate bitflags;
extern crate bit_set;
extern crate cgmath;

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

extern crate wrapped2d;

#[macro_use]
extern crate gfx;
extern crate gfx_device_gl;
extern crate gl;
extern crate glutin;
extern crate image;
extern crate winit;

extern crate pitch_calc;
extern crate portaudio;
extern crate sample;

extern crate itertools;
extern crate num;
extern crate num_traits;
extern crate rand;

#[cfg(feature = "profiler")]
extern crate cpuprofiler;

#[macro_use]
extern crate enum_primitive;
extern crate conrod;

extern crate ctrlc;
extern crate getopts;

extern crate gilrs;

extern crate dirs;
extern crate rayon;
#[cfg(unix)]
extern crate thread_priority;

extern crate rustc_serialize as serialize;

fn main() {
	use log4rs::append::console::*;
	use log4rs::config::*;
	use std::env;
	let args = env::args_os().collect::<Vec<_>>();

	let config = Config::builder()
		.appender(Appender::builder().build("stdout".to_string(), Box::new(ConsoleAppender::builder().build())))
		.logger(Logger::builder().build("gfx_device_gl".to_string(), log::LevelFilter::Error))
		.logger(Logger::builder().build("rust_oids".to_string(), log::LevelFilter::Info))
		.build(
			Root::builder()
				.appender("stdout".to_string())
				.build(log::LevelFilter::Info),
		);
	log4rs::init_config(config.unwrap()).unwrap();

	#[cfg(feature = "profiler")]
	{
		cpuprofiler::PROFILER
			.lock()
			.unwrap()
			.start("./rust-oids.profile")
			.unwrap();
	}

	app::run(&args);

	#[cfg(feature = "profiler")]
	{
		cpuprofiler::PROFILER.lock().unwrap().stop().unwrap();
	}
}
<|endoftext|>


<commit_before>use bit_field::BitField;
use std::fmt::{self, Debug, Display, Formatter};
use thiserror::Error;

#[derive(Clone, Debug, Eq, PartialEq)]
struct FieldInfo {
    name: &'static str,
    start: usize,
    width: usize,
    value: u64,
}

impl FieldInfo {
    fn get(esr: u64, name: &'static str, start: usize, end: usize) -> Self {
        let value = esr.get_bits(start..end);
        Self {
            name,
            start,
            width: end - start,
            value,
        }
    }
}

impl Display for FieldInfo {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        if self.width == 1 {
            write!(
                f,
                "{}:{}",
                self.name,
                if self.value == 1 { "true" } else { "false" }
            )
        } else {
            write!(f, "{}:{:#x}", self.name, self.value)
        }
    }
}

#[derive(Debug, Error)]
enum DecodeError {
    #[error("Invalid ESR, res0 is {res0}")]
    InvalidRes0 { res0: u64 },
    #[error("Invalid EC {ec}")]
    InvalidEc { ec: u64 },
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum SyndromeAccessSize {
    Byte = 0b00,
    Halfword = 0b01,
    Word = 0b10,
    Doubleword = 0b11,
}

impl Display for SyndromeAccessSize {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let s = match self {
            Self::Byte => "byte",
            Self::Halfword => "halfword",
            Self::Word => "word",
            Self::Doubleword => "doubleword",
        };
        write!(f, "{}", s)
    }
}

fn decode_iss_data_abort(iss: u64) -> String {
    let isv = iss.get_bit(24);
    let sas = match iss.get_bits(22..24) {
        0b00 => SyndromeAccessSize::Byte,
        0b01 => SyndromeAccessSize::Halfword,
        0b10 => SyndromeAccessSize::Word,
        0b11 => SyndromeAccessSize::Doubleword,
        _ => unreachable!(),
    };
    let sse = iss.get_bit(21);
    let src = iss.get_bits(16..21);
    let sf = iss.get_bit(15);
    let ar = iss.get_bit(14);
    let vncr = iss.get_bit(13);
    let fnv = iss.get_bit(10);
    let ea = iss.get_bit(9);
    let cm = iss.get_bit(8);
    let s1ptw = iss.get_bit(7);
    let wnr = iss.get_bit(6);
    let dfsc = iss.get_bits(0..6);
    format!("ISV:{}, SAS:{}", isv, sas)
}

fn decode(esr: u64) -> Result<String, DecodeError> {
    let res0 = FieldInfo::get(esr, "RES0", 37, 64);
    let iss2 = FieldInfo::get(esr, "ISS2", 32, 37);
    let ec = FieldInfo::get(esr, "EC", 26, 32);
    let il = FieldInfo::get(esr, "IL", 25, 26);
    let iss = FieldInfo::get(esr, "ISS", 0, 25);
    if res0.value != 0 {
        return Err(DecodeError::InvalidRes0 { res0: res0.value });
    }
    let (class, iss_description) = match ec.value {
        0b000000 => ("Unknown reason", None),
        0b000001 => ("Wrapped WF* instruction execution", None),
        0b000011 => ("Trapped MCR or MRC access with coproc=0b1111", None),
        0b000100 => ("Trapped MCRR or MRRC access with coproc=0b1111", None),
        0b000101 => ("Trapped MCR or MRC access with coproc=0b1110", None),
        0b000110 => ("Trapped LDC or STC access", None),
        0b000111 => ("Trapped access to SVE, Advanced SIMD or floating point", None),
        0b001010 => ("Trapped execution of an LD64B, ST64B, ST64BV, or ST64BV0 instruction", None),
        0b001100 => ("Trapped MRRC access with (coproc==0b1110)", None),
        0b001101 => ("Branch Target Exception", None),
        0b001110 => ("Illegal Execution state", None),
        0b010001 => ("SVC instruction execution in AArch32 state", None),
        0b010101 => ("SVC instruction execution in AArch64 state", None),
        0b011000 => ("Trapped MSR, MRS or System instruction execution in AArch64 state", None),
        0b011001 => ("Access to SVE functionality trapped as a result of CPACR_EL1.ZEN, CPTR_EL2.ZEN, CPTR_EL2.TZ, or CPTR_EL3.EZ", None),
        0b011100 => ("Exception from a Pointer Authentication instruction authentication failure", None),
        0b100000 => ("Instruction Abort from a lower Exception level", None),
        0b100001 => ("Instruction Abort taken without a change in Exception level", None),
        0b100010 => ("PC alignment fault exception", None),
        0b100100 => ("Data Abort from a lower Exception level", Some(decode_iss_data_abort(iss.value))),
        0b100101 => ("Data Abort taken without a change in Exception level", Some(decode_iss_data_abort(iss.value))),
        0b100110 => ("SP alignment fault exception", None),
        0b101000 => ("Trapped floating-point exception taken from AArch32 state", None),
        0b101100 => ("Trapped floating-point exception taken from AArch64 state", None),
        0b101111 => ("SError interrupt", None),
        0b110000 => ("Breakpoint exception from a lower Exception level", None),
        0b110001 => ("Breakpoint exception taken without a change in Exception level", None),
        0b110010 => ("Software Step exception from a lower Exception level", None),
        0b110011 => ("Software Step exception taken without a change in Exception level", None),
        0b110100 => ("Watchpoint exception from a lower Exception level", None),
        0b110101 => ("Watchpoint exception taken without a change in Exception level", None),
        0b111000 => ("BKPT instruction execution in AArch32 state", None),
        0b111100 => ("BRK instruction execution in AArch64 state", None),
        _ => return Err(DecodeError::InvalidEc { ec: ec.value }),
    };
    if let Some(iss_description) = iss_description {
        Ok(format!(
            "EC:{:#08b} '{}', {}, {} ({}), {}",
            ec.value, class, il, iss, iss_description, iss2
        ))
    } else {
        Ok(format!(
            "EC:{:#08b} '{}', {}, {}, {}",
            ec.value, class, il, iss, iss2
        ))
    }
}

fn main() {
    let esr = 2516582480;
    println!("{:#034x}: {}", esr, decode(esr).unwrap());
}
<commit_msg>Return list of fields along with description.<commit_after>use bit_field::BitField;
use std::fmt::{self, Debug, Display, Formatter};
use thiserror::Error;

#[derive(Clone, Debug, Eq, PartialEq)]
struct FieldInfo {
    name: &'static str,
    start: usize,
    width: usize,
    value: u64,
}

impl FieldInfo {
    fn get(esr: u64, name: &'static str, start: usize, end: usize) -> Self {
        let value = esr.get_bits(start..end);
        Self {
            name,
            start,
            width: end - start,
            value,
        }
    }
}

impl Display for FieldInfo {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        if self.width == 1 {
            write!(
                f,
                "{}:{}",
                self.name,
                if self.value == 1 { "true" } else { "false" }
            )
        } else {
            write!(f, "{}:{:#x}", self.name, self.value)
        }
    }
}

#[derive(Debug, Error)]
enum DecodeError {
    #[error("Invalid ESR, res0 is {res0}")]
    InvalidRes0 { res0: u64 },
    #[error("Invalid EC {ec}")]
    InvalidEc { ec: u64 },
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum SyndromeAccessSize {
    Byte = 0b00,
    Halfword = 0b01,
    Word = 0b10,
    Doubleword = 0b11,
}

impl Display for SyndromeAccessSize {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let s = match self {
            Self::Byte => "byte",
            Self::Halfword => "halfword",
            Self::Word => "word",
            Self::Doubleword => "doubleword",
        };
        write!(f, "{}", s)
    }
}

fn decode_iss_data_abort(iss: u64) -> String {
    let isv = iss.get_bit(24);
    let sas = match iss.get_bits(22..24) {
        0b00 => SyndromeAccessSize::Byte,
        0b01 => SyndromeAccessSize::Halfword,
        0b10 => SyndromeAccessSize::Word,
        0b11 => SyndromeAccessSize::Doubleword,
        _ => unreachable!(),
    };
    let sse = iss.get_bit(21);
    let src = iss.get_bits(16..21);
    let sf = iss.get_bit(15);
    let ar = iss.get_bit(14);
    let vncr = iss.get_bit(13);
    let fnv = iss.get_bit(10);
    let ea = iss.get_bit(9);
    let cm = iss.get_bit(8);
    let s1ptw = iss.get_bit(7);
    let wnr = iss.get_bit(6);
    let dfsc = iss.get_bits(0..6);
    format!("ISV:{}, SAS:{}", isv, sas)
}

struct Decoded {
    fields: Vec<FieldInfo>,
    description: String,
}

fn decode(esr: u64) -> Result<Decoded, DecodeError> {
    let res0 = FieldInfo::get(esr, "RES0", 37, 64);
    let iss2 = FieldInfo::get(esr, "ISS2", 32, 37);
    let ec = FieldInfo::get(esr, "EC", 26, 32);
    let il = FieldInfo::get(esr, "IL", 25, 26);
    let iss = FieldInfo::get(esr, "ISS", 0, 25);
    if res0.value != 0 {
        return Err(DecodeError::InvalidRes0 { res0: res0.value });
    }
    let (class, iss_description) = match ec.value {
        0b000000 => ("Unknown reason", None),
        0b000001 => ("Wrapped WF* instruction execution", None),
        0b000011 => ("Trapped MCR or MRC access with coproc=0b1111", None),
        0b000100 => ("Trapped MCRR or MRRC access with coproc=0b1111", None),
        0b000101 => ("Trapped MCR or MRC access with coproc=0b1110", None),
        0b000110 => ("Trapped LDC or STC access", None),
        0b000111 => ("Trapped access to SVE, Advanced SIMD or floating point", None),
        0b001010 => ("Trapped execution of an LD64B, ST64B, ST64BV, or ST64BV0 instruction", None),
        0b001100 => ("Trapped MRRC access with (coproc==0b1110)", None),
        0b001101 => ("Branch Target Exception", None),
        0b001110 => ("Illegal Execution state", None),
        0b010001 => ("SVC instruction execution in AArch32 state", None),
        0b010101 => ("SVC instruction execution in AArch64 state", None),
        0b011000 => ("Trapped MSR, MRS or System instruction execution in AArch64 state", None),
        0b011001 => ("Access to SVE functionality trapped as a result of CPACR_EL1.ZEN, CPTR_EL2.ZEN, CPTR_EL2.TZ, or CPTR_EL3.EZ", None),
        0b011100 => ("Exception from a Pointer Authentication instruction authentication failure", None),
        0b100000 => ("Instruction Abort from a lower Exception level", None),
        0b100001 => ("Instruction Abort taken without a change in Exception level", None),
        0b100010 => ("PC alignment fault exception", None),
        0b100100 => ("Data Abort from a lower Exception level", Some(decode_iss_data_abort(iss.value))),
        0b100101 => ("Data Abort taken without a change in Exception level", Some(decode_iss_data_abort(iss.value))),
        0b100110 => ("SP alignment fault exception", None),
        0b101000 => ("Trapped floating-point exception taken from AArch32 state", None),
        0b101100 => ("Trapped floating-point exception taken from AArch64 state", None),
        0b101111 => ("SError interrupt", None),
        0b110000 => ("Breakpoint exception from a lower Exception level", None),
        0b110001 => ("Breakpoint exception taken without a change in Exception level", None),
        0b110010 => ("Software Step exception from a lower Exception level", None),
        0b110011 => ("Software Step exception taken without a change in Exception level", None),
        0b110100 => ("Watchpoint exception from a lower Exception level", None),
        0b110101 => ("Watchpoint exception taken without a change in Exception level", None),
        0b111000 => ("BKPT instruction execution in AArch32 state", None),
        0b111100 => ("BRK instruction execution in AArch64 state", None),
        _ => return Err(DecodeError::InvalidEc { ec: ec.value }),
    };
    let description = if let Some(iss_description) = iss_description {
        format!(
            "EC:{:#08b} '{}', {}, {} ({}), {}",
            ec.value, class, il, iss, iss_description, iss2
        )
    } else {
        format!(
            "EC:{:#08b} '{}', {}, {}, {}",
            ec.value, class, il, iss, iss2
        )
    };
    Ok(Decoded {
        fields: vec![res0, iss2, ec, il, iss],
        description,
    })
}

fn main() {
    let esr = 2516582480;
    let decoded = decode(esr).unwrap();
    println!("{:#034x}: {}", esr, decoded.description);
    for field in decoded.fields {
        if field.width == 1 {
            println!("{:02}    {}", field.start, field);
        } else {
            println!(
                "{:02}-{:02} {}",
                field.start + field.width,
                field.start,
                field
            );
        }
    }
}
<|endoftext|>


<commit_before>/*
	Full Monkey.
	Generic preprocessor tool.

	Copyright 2016 Sam Saint-Pettersen.
    Released under the MIT/X11 License.
*/

extern crate clioptions;
extern crate regex;
use clioptions::CliOptions;
use regex::Regex;
use std::io::{BufRead, BufReader, Write};
use std::fs::File;
use std::process::exit;

fn preprocess(input: &str, output: &str, conditions: &str, verbose: bool) {
    let mut loc: Vec<String> = Vec::new();
    let mut preprocessed: Vec<String> = Vec::new();
    let mut cond = String::new();
    let mut set_conds: Vec<String> = Vec::new();
    let mut prefixed: Vec<String> = Vec::new();
    let mut prefixes: Vec<String> = Vec::new();
    let mut in_pp = false;

    let conditions = conditions.split(",");
    for sc in conditions {
        set_conds.push(sc.to_string());
    }

    let f = File::open(input).unwrap();
    let file = BufReader::new(&f);
    for l in file.lines() {
        let l = l.unwrap();

        // Process prefix...
        let mut p = Regex::new("#prefix (.*) with (.*)").unwrap();
        if p.is_match(&l) {
            for cap in p.captures_iter(&l) {
                prefixed.push(cap.at(1).unwrap().to_string());
                prefixes.push(cap.at(2).unwrap().to_string());

            }
            continue;
        }
        // Process conditional (if/else/elseif)...
        p = Regex::new("#[else]*[if]* (.*)").unwrap();
        if p.is_match(&l) {
            for cap in p.captures_iter(&l) {
                cond = cap.at(1).unwrap().to_string();
                in_pp = true;
            }
            continue;
        }

        // Process end block...
        p = Regex::new("#[fi|endif]").unwrap();
        if p.is_match(&l) {
            in_pp = false;
            continue;
        }
        // Push relevant LoC to vector...
        for sc in set_conds.clone() {
            if in_pp && cond == sc {
                preprocessed.push(l.to_string());
            }
        }
        if !in_pp {
            preprocessed.push(l.to_string());
            continue;
        }
    }
    // Do any alterations:
    for line in preprocessed {
        let mut fl = line;
        for (i, p) in prefixed.iter().enumerate() {
            let r = Regex::new(&regex::quote(&p)).unwrap();
            let repl = format!("{}{}", &prefixes[i], &p);
            fl = r.replace_all(&fl, &repl[..]);
        }
        loc.push(fl);
    }
    loc.push(String::new());

    if verbose {
        println!("Preprocessing {} --> {}", input, output);
    }

    let mut w = File::create(output).unwrap();
    let _ = w.write_all(loc.join("\n").as_bytes());
}

fn display_version() {
	println!("Full Monkey v. 0.1");
	println!(r"     __");
    println!(r"w  c(..)o   (");
    println!(r" \__(-)    __)");
    println!(r"     /\   (");
    println!(r"    /(_)___)");
    println!(r"   w /|");
    println!(r"    | \");
    println!(r"   m  m");
    println!("\nMonkey appears courtesy of ejm97:");
    println!("http://www.ascii-art.de/ascii/mno/monkey.txt");
	exit(0);
}

fn display_error(program: &str, error: &str) {
	println!("Error: {}.", error);
	display_usage(&program, -1);
}

fn display_usage(program: &str, exit_code: i32) {
	println!("\nFull Monkey.");
	println!("Generic preprocessor tool.");
    println!("\nCopyright 2016 Sam Saint-Pettersen.");
    println!("Released under the MIT/X11 License.");
	println!("\n{} -f|--file <input> [-c|--condition <condition(s)>] -o|--out <output>", program);
	println!("[-l|--verbose][-h|--help | -v|--version]");
	println!("\n-f|--file: File to run preprocessing on.");
	println!("-c|--conditon: Comma delimited list of conditon(s) to apply.");
	println!("-o|--out: File to output preprocessed LoC to.");
    println!("-l|--verbose: Display output to console on process.");
	println!("-h|--help: Display this help information and exit.");
	println!("-v|--version: Display program versiona and exit.");
	exit(exit_code);
}

fn main() {
    let cli = CliOptions::new("fm");
    let program = cli.get_program();
    let mut input = String::new();
    let mut output = String::new();
    let mut conditions = String::new();
    let mut verbose = false;
    if cli.get_num() > 1 {
    	for (i, a) in cli.get_args().iter().enumerate() {
    		match a.trim() {
    			"-h" | "--help" => display_usage(&program, 0),
    			"-v" | "--version" => display_version(),
                "-f" | "--file" => input = cli.next_argument(i),
                "-c" | "--condition" => conditions = cli.next_argument(i),
                "-o" | "--out" => output = cli.next_argument(i),
                "-l" | "--verbose" => verbose = true,
    			_ => continue,
    		}
        }

        if input.is_empty() {
            display_error(&program, "No input specified");
        }

        if output.is_empty() {
            display_error(&program, "No output specified");
        }

        preprocess(&input, &output, &conditions, verbose);
    }
    else {
    	display_error(&program, "No options specified");
    }
}
<commit_msg>fix indenting.<commit_after>/*
    Full Monkey.
    Generic preprocessor tool.

    Copyright 2016 Sam Saint-Pettersen.
    Released under the MIT/X11 License.
*/

extern crate clioptions;
extern crate regex;
use clioptions::CliOptions;
use regex::Regex;
use std::io::{BufRead, BufReader, Write};
use std::fs::File;
use std::process::exit;

fn preprocess(input: &str, output: &str, conditions: &str, verbose: bool) {
    let mut loc: Vec<String> = Vec::new();
    let mut preprocessed: Vec<String> = Vec::new();
    let mut cond = String::new();
    let mut set_conds: Vec<String> = Vec::new();
    let mut prefixed: Vec<String> = Vec::new();
    let mut prefixes: Vec<String> = Vec::new();
    let mut in_pp = false;

    let conditions = conditions.split(",");
    for sc in conditions {
        set_conds.push(sc.to_string());
    }

    let f = File::open(input).unwrap();
    let file = BufReader::new(&f);
    for l in file.lines() {
        let l = l.unwrap();

        // Process prefix...
        let mut p = Regex::new("#prefix (.*) with (.*)").unwrap();
        if p.is_match(&l) {
            for cap in p.captures_iter(&l) {
                prefixed.push(cap.at(1).unwrap().to_string());
                prefixes.push(cap.at(2).unwrap().to_string());

            }
            continue;
        }
        // Process conditional (if/else/elseif)...
        p = Regex::new("#[else]*[if]* (.*)").unwrap();
        if p.is_match(&l) {
            for cap in p.captures_iter(&l) {
                cond = cap.at(1).unwrap().to_string();
                in_pp = true;
            }
            continue;
        }

        // Process end block...
        p = Regex::new("#[fi|endif]").unwrap();
        if p.is_match(&l) {
            in_pp = false;
            continue;
        }
        // Push relevant LoC to vector...
        for sc in set_conds.clone() {
            if in_pp && cond == sc {
                preprocessed.push(l.to_string());
            }
        }
        if !in_pp {
            preprocessed.push(l.to_string());
            continue;
        }
    }
    // Do any alterations:
    for line in preprocessed {
        let mut fl = line;
        for (i, p) in prefixed.iter().enumerate() {
            let r = Regex::new(&regex::quote(&p)).unwrap();
            let repl = format!("{}{}", &prefixes[i], &p);
            fl = r.replace_all(&fl, &repl[..]);
        }
        loc.push(fl);
    }
    loc.push(String::new());

    if verbose {
        println!("Preprocessing {} --> {}", input, output);
    }

    let mut w = File::create(output).unwrap();
    let _ = w.write_all(loc.join("\n").as_bytes());
}

fn display_version() {
    println!("Full Monkey v. 0.1");
    println!(r"     __");
    println!(r"w  c(..)o   (");
    println!(r" \__(-)    __)");
    println!(r"     /\   (");
    println!(r"    /(_)___)");
    println!(r"   w /|");
    println!(r"    | \");
    println!(r"   m  m");
    println!("\nMonkey appears courtesy of ejm97:");
    println!("http://www.ascii-art.de/ascii/mno/monkey.txt");
    exit(0);
}

fn display_error(program: &str, error: &str) {
    println!("Error: {}.", error);
    display_usage(&program, -1);
}

fn display_usage(program: &str, exit_code: i32) {
    println!("\nFull Monkey.");
    println!("Generic preprocessor tool.");
    println!("\nCopyright 2016 Sam Saint-Pettersen.");
    println!("Released under the MIT/X11 License.");
    println!("\n{} -f|--file <input> [-c|--condition <condition(s)>] -o|--out <output>", program);
    println!("[-l|--verbose][-h|--help | -v|--version]");
    println!("\n-f|--file: File to run preprocessing on.");
    println!("-c|--conditon: Comma delimited list of conditon(s) to apply.");
    println!("-o|--out: File to output preprocessed LoC to.");
    println!("-l|--verbose: Display output to console on process.");
    println!("-h|--help: Display this help information and exit.");
    println!("-v|--version: Display program versiona and exit.");
    exit(exit_code);
}

fn main() {
    let cli = CliOptions::new("fm");
    let program = cli.get_program();
    let mut input = String::new();
    let mut output = String::new();
    let mut conditions = String::new();
    let mut verbose = false;
    if cli.get_num() > 1 {
        for (i, a) in cli.get_args().iter().enumerate() {
            match a.trim() {
                "-h" | "--help" => display_usage(&program, 0),
                "-v" | "--version" => display_version(),
                "-f" | "--file" => input = cli.next_argument(i),
                "-c" | "--condition" => conditions = cli.next_argument(i),
                "-o" | "--out" => output = cli.next_argument(i),
                "-l" | "--verbose" => verbose = true,
                _ => continue,
            }
        }

        if input.is_empty() {
            display_error(&program, "No input specified");
        }

        if output.is_empty() {
            display_error(&program, "No output specified");
        }

        preprocess(&input, &output, &conditions, verbose);
    }
    else {
        display_error(&program, "No options specified");
    }
}
<|endoftext|>


<commit_before>fn main() {
    println!("Hello, world!");
}
<commit_msg>単純な実装をとりあえず作成<commit_after>use std::cmp::min;

fn main() {
    let bv = [0, 1, 0, 1];
    println!("rank:{:?}", rank_mock(&bv, 0));
    println!("select:{:?}", select_mock(&bv, 1));
}

fn rank_mock(bv: &[u8], idx: usize) -> usize {
    let mut ret = 0;
    let len = min(bv.len(), idx);

    for i in 0..len {
        if bv[i]==1 {
            ret = ret + 1;
        }
    }

    ret
}

fn select_mock(bv: &[u8], idx: usize) -> usize {
    let mut cnt = 0;
    for i in 0..bv.len() {
        if bv[i]==1 {
            cnt = cnt+1;
        }
        if cnt==idx+1 {
            return i;
        }
    }
    return 0;
}

#[test]
fn rank_test() {
    let bv = [0,1,0,1];
    assert_eq!(rank_mock(&bv, 0), 0);
    assert_eq!(rank_mock(&bv, 1), 0);
    assert_eq!(rank_mock(&bv, 2), 1);
    assert_eq!(rank_mock(&bv, 3), 1);
    assert_eq!(rank_mock(&bv, 4), 2);
    assert_eq!(rank_mock(&bv, 5), 2);
}

#[test]
fn select_test() {
    let bv = [0,1,0,1];
    assert_eq!(select_mock(&bv, 0), 1);
    assert_eq!(select_mock(&bv, 1), 3);
    assert_eq!(select_mock(&bv, 2), 0);
}
<|endoftext|>