<commit_before>use std::ptr;

use baseline::fct::{CatchType, JitFctId};
use baseline::map::CodeData;
use cpu::{fp_from_execstate, get_exception_object, resume_with_handler};
use ctxt::{get_ctxt, SemContext};
use object::{alloc, Array, Exception, Handle, IntArray, Obj, StackTraceElement, Str};
use execstate::ExecState;

pub struct Stacktrace {
    elems: Vec<StackElem>,
}

impl Stacktrace {
    pub fn new() -> Stacktrace {
        Stacktrace { elems: Vec::new() }
    }

    pub fn len(&self) -> usize {
        self.elems.len()
    }

    pub fn push_entry(&mut self, fct_id: JitFctId, lineno: i32) {
        self.elems.push(StackElem {
            fct_id: fct_id,
            lineno: lineno,
        });
    }

    pub fn dump(&self, ctxt: &SemContext) {
        for (ind, elem) in self.elems.iter().enumerate() {
            let jit_fct = ctxt.jit_fcts[elem.fct_id].borrow();
            let fct_id = jit_fct.fct_id;
            let fct = ctxt.fcts[fct_id].borrow();
            let name = fct.full_name(ctxt);
            print!("{}: {}: ", ind, name);

            if elem.lineno == 0 {
                println!("?");
            } else {
                println!("{}", elem.lineno);
            }
        }
    }
}

struct StackElem {
    fct_id: JitFctId,
    lineno: i32,
}

pub struct DoraToNativeInfo {
    // pointer to previous DTN-Info
    pub last: *const DoraToNativeInfo,

    // frame pointer of native stub
    pub fp: usize,

    // some program counter into native stub
    pub pc: usize,
}

impl DoraToNativeInfo {
    pub fn new() -> DoraToNativeInfo {
        DoraToNativeInfo {
            last: ptr::null(),
            fp: 0,
            pc: 0,
        }
    }
}

pub fn stacktrace_from_es(ctxt: &SemContext, es: &ExecState) -> Stacktrace {
    let mut stacktrace = Stacktrace::new();
    let fp = fp_from_execstate(es);
    frames_from_pc(&mut stacktrace, ctxt, es.pc, fp);
    frames_from_dtns(&mut stacktrace, ctxt);
    return stacktrace;
}

pub fn stacktrace_from_last_dtn(ctxt: &SemContext) -> Stacktrace {
    let mut stacktrace = Stacktrace::new();
    frames_from_dtns(&mut stacktrace, ctxt);
    return stacktrace;
}

fn frames_from_dtns(stacktrace: &mut Stacktrace, ctxt: &SemContext) {
    let mut dtn_ptr = *ctxt.dtn.borrow();

    while !dtn_ptr.is_null() {
        let dtn = unsafe { &*dtn_ptr };

        let pc: usize = dtn.pc;
        let fp: usize = dtn.fp;

        frames_from_pc(stacktrace, ctxt, pc, fp);

        dtn_ptr = dtn.last
    }
}

fn frames_from_pc(stacktrace: &mut Stacktrace, ctxt: &SemContext, pc: usize, mut fp: usize) {
    if !determine_stack_entry(stacktrace, ctxt, pc) {
        return;
    }

    while fp != 0 {
        let ra = unsafe { *((fp + 8) as *const usize) };

        if !determine_stack_entry(stacktrace, ctxt, ra) {
            return;
        }

        fp = unsafe { *(fp as *const usize) };
    }
}

fn determine_stack_entry(stacktrace: &mut Stacktrace, ctxt: &SemContext, pc: usize) -> bool {
    let code_map = ctxt.code_map.lock().unwrap();
    let data = code_map.get(pc as *const u8);

    match data {
        Some(CodeData::Fct(fct_id)) => {
            let jit_fct = ctxt.jit_fcts[fct_id].borrow();

            let offset = pc - (jit_fct.fct_ptr() as usize);
            let lineno = jit_fct.lineno_for_offset(offset as i32);

            if lineno == 0 {
                panic!("lineno not found for program point");
            }

            stacktrace.push_entry(fct_id, lineno);

            true
        }

        Some(CodeData::NativeStub(fct_id)) => {
            let jit_fct = ctxt.jit_fcts[fct_id].borrow();
            let fct = ctxt.fcts[jit_fct.fct_id].borrow();

            stacktrace.push_entry(fct_id, fct.ast.pos.line as i32);

            true
        }

        _ => false
    }
}

pub fn handle_exception(es: &mut ExecState) -> bool {
    let mut pc: usize = es.pc;
    let mut fp: usize = fp_from_execstate(es);

    let exception = get_exception_object(es);

    loop {
        let found = find_handler(exception, es, pc, fp);

        match found {
            HandlerFound::Yes => {
                return true;
            }
            HandlerFound::Stop => {
                return false;
            }
            HandlerFound::No => if fp == 0 {
                return false;
            },
        }

        pc = unsafe { *((fp + 8) as *const usize) };
        fp = unsafe { *(fp as *const usize) };
    }
}

#[derive(PartialEq, Eq, Debug)]
enum HandlerFound {
    Yes,
    No,
    Stop,
}

fn find_handler(exception: Handle<Obj>, es: &mut ExecState, pc: usize, fp: usize) -> HandlerFound {
    let ctxt = get_ctxt();
    let data = {
        let code_map = ctxt.code_map.lock().unwrap();
        code_map.get(pc as *const u8)
    };

    if data.is_none() {
        return HandlerFound::No;
    }

    if let CodeData::Fct(fct_id) = data.unwrap() {
        let jit_fct = ctxt.jit_fcts[fct_id].borrow();
        let clsptr = exception.header().vtbl().classptr();

        for entry in &jit_fct.exception_handlers {
            // println!("entry = {:x} to {:x} for {:?}",
            //          entry.try_start, entry.try_end, entry.catch_type);

            if entry.try_start < pc && pc <= entry.try_end &&
                (entry.catch_type == CatchType::Any || entry.catch_type == CatchType::Class(clsptr))
            {
                let stacksize = jit_fct.framesize as usize;
                resume_with_handler(es, entry, fp, exception, stacksize);

                return HandlerFound::Yes;
            } else if pc > entry.try_end {
                // exception handlers are sorted, no more possible handlers
                // in this function

                return HandlerFound::No;
            }
        }

        // exception can only bubble up in stacktrace if current function
        // is allowed to throw exceptions
        if !jit_fct.throws {
            return HandlerFound::Stop;
        }
    }

    HandlerFound::No
}

pub extern "C" fn retrieve_stack_trace(obj: Handle<Exception>) {
    let ctxt = get_ctxt();
    set_exception_backtrace(ctxt, obj, true);
}

pub extern "C" fn stack_element(obj: Handle<Exception>, ind: i32) -> Handle<StackTraceElement> {
    let ctxt = get_ctxt();
    let obj = ctxt.handles.root(obj);
    let array = obj.backtrace;

    let ind = ind as usize * 2;

    let lineno = array.get_at(ind);
    let fct_id = array.get_at(ind + 1);
    let cls_def_id = ctxt.vips.stack_trace_element(ctxt);

    let ste: Handle<StackTraceElement> = alloc(ctxt, cls_def_id).cast();
    let mut ste = ctxt.handles.root(ste);
    ste.line = lineno;

    let jit_fct_id = JitFctId::from(fct_id as usize);
    let jit_fct = ctxt.jit_fcts[jit_fct_id].borrow();
    let fct = ctxt.fcts[jit_fct.fct_id].borrow();
    let name = fct.full_name(ctxt);
    ste.name = Str::from_buffer(ctxt, name.as_bytes());

    ste.direct()
}

pub fn alloc_exception(ctxt: &SemContext, msg: Handle<Str>) -> Handle<Exception> {
    let cls_id = ctxt.vips.exception(ctxt);
    let obj: Handle<Exception> = alloc(ctxt, cls_id).cast();
    let mut obj = ctxt.handles.root(obj);

    obj.msg = msg;
    set_exception_backtrace(ctxt, obj.direct(), false);

    obj.direct()
}

fn set_exception_backtrace(ctxt: &SemContext, obj: Handle<Exception>, via_retrieve: bool) {
    let stacktrace = stacktrace_from_last_dtn(ctxt);
    let mut obj = ctxt.handles.root(obj);

    let skip = if via_retrieve { 2 } else { 0 };
    let len = stacktrace.len() - skip;

    let cls_id = ctxt.vips.int_array(ctxt);
    let array: Handle<IntArray> = Array::alloc(ctxt, len * 2, 0, cls_id);
    let mut array = ctxt.handles.root(array);
    let mut i = 0;

    // ignore first element of stack trace (ctor of Exception)
    for elem in stacktrace.elems.iter().skip(skip) {
        array.set_at(i, elem.lineno);
        array.set_at(i + 1, elem.fct_id.idx() as i32);

        i += 2;
    }

    obj.backtrace = array.direct();
}
<commit_msg>fix find_handler for native stub<commit_after>use std::ptr;

use baseline::fct::{CatchType, JitFctId};
use baseline::map::CodeData;
use cpu::{fp_from_execstate, get_exception_object, resume_with_handler};
use ctxt::{get_ctxt, SemContext};
use object::{alloc, Array, Exception, Handle, IntArray, Obj, StackTraceElement, Str};
use execstate::ExecState;

pub struct Stacktrace {
    elems: Vec<StackElem>,
}

impl Stacktrace {
    pub fn new() -> Stacktrace {
        Stacktrace { elems: Vec::new() }
    }

    pub fn len(&self) -> usize {
        self.elems.len()
    }

    pub fn push_entry(&mut self, fct_id: JitFctId, lineno: i32) {
        self.elems.push(StackElem {
            fct_id: fct_id,
            lineno: lineno,
        });
    }

    pub fn dump(&self, ctxt: &SemContext) {
        for (ind, elem) in self.elems.iter().enumerate() {
            let jit_fct = ctxt.jit_fcts[elem.fct_id].borrow();
            let fct_id = jit_fct.fct_id;
            let fct = ctxt.fcts[fct_id].borrow();
            let name = fct.full_name(ctxt);
            print!("{}: {}: ", ind, name);

            if elem.lineno == 0 {
                println!("?");
            } else {
                println!("{}", elem.lineno);
            }
        }
    }
}

struct StackElem {
    fct_id: JitFctId,
    lineno: i32,
}

pub struct DoraToNativeInfo {
    // pointer to previous DTN-Info
    pub last: *const DoraToNativeInfo,

    // frame pointer of native stub
    pub fp: usize,

    // some program counter into native stub
    pub pc: usize,
}

impl DoraToNativeInfo {
    pub fn new() -> DoraToNativeInfo {
        DoraToNativeInfo {
            last: ptr::null(),
            fp: 0,
            pc: 0,
        }
    }
}

pub fn stacktrace_from_es(ctxt: &SemContext, es: &ExecState) -> Stacktrace {
    let mut stacktrace = Stacktrace::new();
    let fp = fp_from_execstate(es);
    frames_from_pc(&mut stacktrace, ctxt, es.pc, fp);
    frames_from_dtns(&mut stacktrace, ctxt);
    return stacktrace;
}

pub fn stacktrace_from_last_dtn(ctxt: &SemContext) -> Stacktrace {
    let mut stacktrace = Stacktrace::new();
    frames_from_dtns(&mut stacktrace, ctxt);
    return stacktrace;
}

fn frames_from_dtns(stacktrace: &mut Stacktrace, ctxt: &SemContext) {
    let mut dtn_ptr = *ctxt.dtn.borrow();

    while !dtn_ptr.is_null() {
        let dtn = unsafe { &*dtn_ptr };

        let pc: usize = dtn.pc;
        let fp: usize = dtn.fp;

        frames_from_pc(stacktrace, ctxt, pc, fp);

        dtn_ptr = dtn.last
    }
}

fn frames_from_pc(stacktrace: &mut Stacktrace, ctxt: &SemContext, pc: usize, mut fp: usize) {
    if !determine_stack_entry(stacktrace, ctxt, pc) {
        return;
    }

    while fp != 0 {
        let ra = unsafe { *((fp + 8) as *const usize) };

        if !determine_stack_entry(stacktrace, ctxt, ra) {
            return;
        }

        fp = unsafe { *(fp as *const usize) };
    }
}

fn determine_stack_entry(stacktrace: &mut Stacktrace, ctxt: &SemContext, pc: usize) -> bool {
    let code_map = ctxt.code_map.lock().unwrap();
    let data = code_map.get(pc as *const u8);

    match data {
        Some(CodeData::Fct(fct_id)) => {
            let jit_fct = ctxt.jit_fcts[fct_id].borrow();

            let offset = pc - (jit_fct.fct_ptr() as usize);
            let lineno = jit_fct.lineno_for_offset(offset as i32);

            if lineno == 0 {
                panic!("lineno not found for program point");
            }

            stacktrace.push_entry(fct_id, lineno);

            true
        }

        Some(CodeData::NativeStub(fct_id)) => {
            let jit_fct = ctxt.jit_fcts[fct_id].borrow();
            let fct = ctxt.fcts[jit_fct.fct_id].borrow();

            stacktrace.push_entry(fct_id, fct.ast.pos.line as i32);

            true
        }

        _ => false
    }
}

pub fn handle_exception(es: &mut ExecState) -> bool {
    let mut pc: usize = es.pc;
    let mut fp: usize = fp_from_execstate(es);

    let exception = get_exception_object(es);

    loop {
        let found = find_handler(exception, es, pc, fp);

        match found {
            HandlerFound::Yes => {
                return true;
            }
            HandlerFound::Stop => {
                return false;
            }
            HandlerFound::No => if fp == 0 {
                return false;
            },
        }

        pc = unsafe { *((fp + 8) as *const usize) };
        fp = unsafe { *(fp as *const usize) };
    }
}

#[derive(PartialEq, Eq, Debug)]
enum HandlerFound {
    Yes,
    No,
    Stop,
}

fn find_handler(exception: Handle<Obj>, es: &mut ExecState, pc: usize, fp: usize) -> HandlerFound {
    let ctxt = get_ctxt();
    let data = {
        let code_map = ctxt.code_map.lock().unwrap();
        code_map.get(pc as *const u8)
    };

    match data {
        Some(CodeData::Fct(fct_id)) |
        Some(CodeData::NativeStub(fct_id)) => {
            let jit_fct = ctxt.jit_fcts[fct_id].borrow();
            let clsptr = exception.header().vtbl().classptr();

            for entry in &jit_fct.exception_handlers {
                // println!("entry = {:x} to {:x} for {:?}",
                //          entry.try_start, entry.try_end, entry.catch_type);

                if entry.try_start < pc && pc <= entry.try_end &&
                    (entry.catch_type == CatchType::Any || entry.catch_type == CatchType::Class(clsptr))
                {
                    let stacksize = jit_fct.framesize as usize;
                    resume_with_handler(es, entry, fp, exception, stacksize);

                    return HandlerFound::Yes;
                } else if pc > entry.try_end {
                    // exception handlers are sorted, no more possible handlers
                    // in this function

                    return HandlerFound::No;
                }
            }

            // exception can only bubble up in stacktrace if current function
            // is allowed to throw exceptions
            if !jit_fct.throws {
                return HandlerFound::Stop;
            }
        }

        _ => {}
    }

    HandlerFound::No
}

pub extern "C" fn retrieve_stack_trace(obj: Handle<Exception>) {
    let ctxt = get_ctxt();
    set_exception_backtrace(ctxt, obj, true);
}

pub extern "C" fn stack_element(obj: Handle<Exception>, ind: i32) -> Handle<StackTraceElement> {
    let ctxt = get_ctxt();
    let obj = ctxt.handles.root(obj);
    let array = obj.backtrace;

    let ind = ind as usize * 2;

    let lineno = array.get_at(ind);
    let fct_id = array.get_at(ind + 1);
    let cls_def_id = ctxt.vips.stack_trace_element(ctxt);

    let ste: Handle<StackTraceElement> = alloc(ctxt, cls_def_id).cast();
    let mut ste = ctxt.handles.root(ste);
    ste.line = lineno;

    let jit_fct_id = JitFctId::from(fct_id as usize);
    let jit_fct = ctxt.jit_fcts[jit_fct_id].borrow();
    let fct = ctxt.fcts[jit_fct.fct_id].borrow();
    let name = fct.full_name(ctxt);
    ste.name = Str::from_buffer(ctxt, name.as_bytes());

    ste.direct()
}

pub fn alloc_exception(ctxt: &SemContext, msg: Handle<Str>) -> Handle<Exception> {
    let cls_id = ctxt.vips.exception(ctxt);
    let obj: Handle<Exception> = alloc(ctxt, cls_id).cast();
    let mut obj = ctxt.handles.root(obj);

    obj.msg = msg;
    set_exception_backtrace(ctxt, obj.direct(), false);

    obj.direct()
}

fn set_exception_backtrace(ctxt: &SemContext, obj: Handle<Exception>, via_retrieve: bool) {
    let stacktrace = stacktrace_from_last_dtn(ctxt);
    let mut obj = ctxt.handles.root(obj);

    let skip = if via_retrieve { 2 } else { 0 };
    let len = stacktrace.len() - skip;

    let cls_id = ctxt.vips.int_array(ctxt);
    let array: Handle<IntArray> = Array::alloc(ctxt, len * 2, 0, cls_id);
    let mut array = ctxt.handles.root(array);
    let mut i = 0;

    // ignore first element of stack trace (ctor of Exception)
    for elem in stacktrace.elems.iter().skip(skip) {
        array.set_at(i, elem.lineno);
        array.set_at(i + 1, elem.fct_id.idx() as i32);

        i += 2;
    }

    obj.backtrace = array.direct();
}
<|endoftext|>


<commit_before>use std::error;
use std::fs;
use std::io;
use std::io::{Read,Write};
use std::mem;
use std::str;
use std::path::Path;

use fat::RootEntry;
use fat::BIOSParam;

// Always the same
const SECTORS_PER_ROOT: usize = 14;
const BYTES_PER_ROOT_ENTRY: usize = 32;

#[test]
fn test_root_entry_size() {
    assert_eq!(mem::size_of::<RootEntry>(), BYTES_PER_ROOT_ENTRY);
}

#[derive(Debug)]
pub struct Image {
    boot_sector: Vec<u8>,
    fat_1: Vec<u8>,
    fat_2: Vec<u8>,
    root_dir: Vec<u8>,
    data_area: Vec<u8>,
    bpb_data: BIOSParam,
}

#[allow(dead_code)]
impl Image {
    /// Create a new blank FAT Image from a defined BPB
    fn new(bpb: BIOSParam, length: usize) -> Image {
        let boot_sector_size = bpb.bytes_per_sector as usize * bpb.reserved_sectors as usize;
        let bytes_per_fat = (bpb.sectors_per_fat * bpb.bytes_per_sector as u32) as usize;
        let bytes_per_root = SECTORS_PER_ROOT * bpb.bytes_per_sector as usize;
        let data_offset = boot_sector_size as usize + (bytes_per_fat * 2) as usize + bytes_per_root as usize;
        let bytes_per_data_area = length - data_offset;
        Image {
            boot_sector: vec![0; boot_sector_size],
            fat_1: vec![0; bytes_per_fat],
            fat_2: vec![0; bytes_per_fat],
            root_dir: vec![0; bytes_per_root],
            data_area: vec![0; bytes_per_data_area],
            bpb_data: bpb,
        }
    }

    /// Create a new FAT Image from the specified file.
    pub fn from_file<P: AsRef<Path>>(p: P)
        -> Result<Image, Box<error::Error>>
    {
        let metadata = fs::metadata(p.as_ref())?;
        let bpb = BIOSParam::from_file(p.as_ref())?;

        let mut file = fs::File::open(p.as_ref())?;
        let mut image = Image::new(bpb, metadata.len() as usize);

        try!(file.read_exact(&mut image.boot_sector));
        try!(file.read_exact(&mut image.fat_1));
        try!(file.read_exact(&mut image.fat_2));
        try!(file.read_exact(&mut image.root_dir));
        try!(file.read_exact(&mut image.data_area));

        Ok(image)
    }

    /// Save the FAT filesystem image to the specified file.
    pub fn save(&self, image_fn: String)
        -> Result<(), io::Error>
    {
        let mut file = fs::File::create(image_fn)?;

        try!(file.write_all(&self.boot_sector));
        try!(file.write_all(&self.fat_1));
        try!(file.write_all(&self.fat_2));
        try!(file.write_all(&self.root_dir));
        try!(file.write_all(&self.data_area));

        Ok(())
    }

    /// Extract the BIOS Parameter Block (BPB) from the FAT filesystem.
    pub fn bios_parameter(&self) -> BIOSParam {
        self.bpb_data.clone()
    }

    /// FAT sector size in bytes.
    pub fn sector_size(&self) -> usize {
        self.bpb_data.bytes_per_sector as usize
    }

    pub fn volume_label(&self) -> Result<String, Box<error::Error>> {
        let entries = self.root_entries();
        for entry in entries {
            if !entry.is_volume_label() {
                continue;
            }
            let label = format!("{}{}",
                str::from_utf8(&entry.filename)?,
                str::from_utf8(&entry.extension)?);
            return Ok(label);
        }
        return Ok("has no label".to_string());
    }

    // TODO: Make this an iterator
    pub fn root_entries(&self) -> Vec<RootEntry> {
        self.root_dir
            .chunks(BYTES_PER_ROOT_ENTRY)
            .map(|chunk| {
                let mut entry_bytes = [0; BYTES_PER_ROOT_ENTRY];
                entry_bytes.clone_from_slice(chunk);

                let entry: RootEntry;
                unsafe { entry = mem::transmute(entry_bytes); }
                entry
            })
            .filter(|entry| { entry.filename[0] != 0xe5 })
            .take_while(|entry| { entry.filename[0] != 0 })
            .collect::<Vec<RootEntry>>()
    }

    /// Get the RootEntry for the specified file within the Image.
    pub fn get_file_entry(&self, filename: String)
        -> Result<RootEntry, Box<error::Error>>
    {
        let entries = self.root_entries();
        for entry in entries {
            let entry_name: String;
            match entry.filename() {
                Ok(name) => entry_name = name,
                Err(_) => continue,
            }

            if entry_name.to_lowercase() == filename.to_lowercase() {
                return Ok(entry);
            }
        }

        Err(From::from(format!("file {} not found", filename)))
    }

    /// Create a new RootEntry within the Image with the specified filename.
    pub fn create_file_entry(&self, filename: String)
        -> Result<(RootEntry, u16), Box<error::Error>>
    {
        for (index, e) in self.root_entries().iter().enumerate() {
            if !e.is_free() { continue; }

            let mut entry = RootEntry::new();
            try!(entry.set_filename(filename));
            return Ok((entry.clone(), index as u16));
        }

        Err(From::from("no free entries"))
    }

    pub fn save_file_entry(&mut self, entry: RootEntry, index: u16)
        -> Result<(), Box<error::Error>>
    {
        let entry_bytes: [u8; BYTES_PER_ROOT_ENTRY];
        unsafe { entry_bytes = mem::transmute(entry); }

        self.root_dir[
            index as usize * BYTES_PER_ROOT_ENTRY ..
            (index as usize + 1) * BYTES_PER_ROOT_ENTRY
        ].clone_from_slice(&entry_bytes[..]);
        Ok(())
    }

    pub fn fat_entries<'a>(&'a self)
        -> Box<Iterator<Item=(usize, u16)> + 'a>
    {
        Box::new(
            self.fat_1
            .windows(2)
            .enumerate()
            .filter_map(|(i, w)|
                if i % 3 == 2 {
                    None
                } else {
                    let val = if i % 3 == 0 {
                        ((w[1] as u16 & 0xf) << 8) | w[0] as u16
                    } else {
                        (w[1] as u16 & 0xf0) | ((w[1] as u16) << 4)
                    };
                    Some((i, val))
                }
            )
        )
    }

    pub fn get_fat_entry(&self, cluster_num: u16) -> u16 {
        let offset: usize = cluster_num as usize * 3 / 2;
        let byte_1: u16 = self.fat_1[offset] as u16;
        let byte_2: u16 = self.fat_1[offset + 1] as u16;

        if cluster_num % 2 == 0 { byte_1 | ((byte_2 & 0x0f) << 8) }
        else                    { (byte_1 >> 4) | (byte_2 << 4) }
    }

    pub fn get_free_fat_entry(&self) -> Option<usize> {
        self.fat_entries()
            .filter(|&(_, e)| e == 0)
            .map(|(i, _)| i + 2)
            .nth(0)
    }

    pub fn write_data_sector(&mut self, sector: usize, data: &[u8])
        -> Result<(), Box<error::Error>>
    {
        let sector = sector - 2;
        let bytes = sector * self.bpb_data.bytes_per_sector as usize;
        if bytes >= self.data_area.len() {
            return Err(From::from(format!("sector {} too high to write to", sector)))
        }

        let mut target_slice = &mut self.data_area[
            self.bpb_data.bytes_per_sector as usize * sector ..
            self.bpb_data.bytes_per_sector as usize * (sector + 1)
        ];

        target_slice.copy_from_slice(data);
        Ok(())
    }
}
<commit_msg>image: Fix no free root entries on add<commit_after>use std::error;
use std::fs;
use std::io;
use std::io::{Read,Write};
use std::mem;
use std::str;
use std::path::Path;

use fat::RootEntry;
use fat::BIOSParam;

// Always the same
const SECTORS_PER_ROOT: usize = 14;
const BYTES_PER_ROOT_ENTRY: usize = 32;

#[test]
fn test_root_entry_size() {
    assert_eq!(mem::size_of::<RootEntry>(), BYTES_PER_ROOT_ENTRY);
}

#[derive(Debug)]
pub struct Image {
    boot_sector: Vec<u8>,
    fat_1: Vec<u8>,
    fat_2: Vec<u8>,
    root_dir: Vec<u8>,
    data_area: Vec<u8>,
    bpb_data: BIOSParam,
}

#[allow(dead_code)]
impl Image {
    /// Create a new blank FAT Image from a defined BPB
    fn new(bpb: BIOSParam, length: usize) -> Image {
        let boot_sector_size = bpb.bytes_per_sector as usize * bpb.reserved_sectors as usize;
        let bytes_per_fat = (bpb.sectors_per_fat * bpb.bytes_per_sector as u32) as usize;
        let bytes_per_root = SECTORS_PER_ROOT * bpb.bytes_per_sector as usize;
        let data_offset = boot_sector_size as usize + (bytes_per_fat * 2) as usize + bytes_per_root as usize;
        let bytes_per_data_area = length - data_offset;
        Image {
            boot_sector: vec![0; boot_sector_size],
            fat_1: vec![0; bytes_per_fat],
            fat_2: vec![0; bytes_per_fat],
            root_dir: vec![0; bytes_per_root],
            data_area: vec![0; bytes_per_data_area],
            bpb_data: bpb,
        }
    }

    /// Create a new FAT Image from the specified file.
    pub fn from_file<P: AsRef<Path>>(p: P)
        -> Result<Image, Box<error::Error>>
    {
        let metadata = fs::metadata(p.as_ref())?;
        let bpb = BIOSParam::from_file(p.as_ref())?;

        let mut file = fs::File::open(p.as_ref())?;
        let mut image = Image::new(bpb, metadata.len() as usize);

        try!(file.read_exact(&mut image.boot_sector));
        try!(file.read_exact(&mut image.fat_1));
        try!(file.read_exact(&mut image.fat_2));
        try!(file.read_exact(&mut image.root_dir));
        try!(file.read_exact(&mut image.data_area));

        Ok(image)
    }

    /// Save the FAT filesystem image to the specified file.
    pub fn save(&self, image_fn: String)
        -> Result<(), io::Error>
    {
        let mut file = fs::File::create(image_fn)?;

        try!(file.write_all(&self.boot_sector));
        try!(file.write_all(&self.fat_1));
        try!(file.write_all(&self.fat_2));
        try!(file.write_all(&self.root_dir));
        try!(file.write_all(&self.data_area));

        Ok(())
    }

    /// Extract the BIOS Parameter Block (BPB) from the FAT filesystem.
    pub fn bios_parameter(&self) -> BIOSParam {
        self.bpb_data.clone()
    }

    /// FAT sector size in bytes.
    pub fn sector_size(&self) -> usize {
        self.bpb_data.bytes_per_sector as usize
    }

    /// FAT volume label
    pub fn volume_label(&self) -> Result<String, Box<error::Error>> {
        let entries = self.root_entries();
        for entry in entries {
            if !entry.is_volume_label() {
                continue;
            }
            let label = format!("{}{}",
                str::from_utf8(&entry.filename)?,
                str::from_utf8(&entry.extension)?);
            return Ok(label);
        }
        return Ok("has no label".to_string());
    }

    // TODO: Make iterator once "impl Trait" is stable.
    /// Return all FAT root entries (including unused)
    pub fn root_entries_all(&self) -> Vec<RootEntry> {
        self.root_dir
            .chunks(BYTES_PER_ROOT_ENTRY)
            .map(|chunk| {
                let mut entry_bytes = [0; BYTES_PER_ROOT_ENTRY];
                entry_bytes.clone_from_slice(chunk);

                let entry: RootEntry;
                unsafe { entry = mem::transmute(entry_bytes); }
                entry
            })
            .collect::<Vec<RootEntry>>()
    }

    // TODO: Make iterator once "impl Trait" is stable.
    /// Return used FAT root entries
    pub fn root_entries(&self) -> Vec<RootEntry> {
        self.root_dir
            .chunks(BYTES_PER_ROOT_ENTRY)
            .map(|chunk| {
                let mut entry_bytes = [0; BYTES_PER_ROOT_ENTRY];
                entry_bytes.clone_from_slice(chunk);

                let entry: RootEntry;
                unsafe { entry = mem::transmute(entry_bytes); }
                entry
            })
            .filter(|entry| { entry.filename[0] != 0xe5 })
            .take_while(|entry| { entry.filename[0] != 0 })
            .collect::<Vec<RootEntry>>()
    }

    /// Get the RootEntry for the specified file within the Image.
    pub fn get_file_entry(&self, filename: String)
        -> Result<RootEntry, Box<error::Error>>
    {
        let entries = self.root_entries();
        for entry in entries {
            let entry_name: String;
            match entry.filename() {
                Ok(name) => entry_name = name,
                Err(_) => continue,
            }

            if entry_name.to_lowercase() == filename.to_lowercase() {
                return Ok(entry);
            }
        }

        Err(From::from(format!("file {} not found", filename)))
    }

    /// Create a new RootEntry within the Image with the specified filename.
    pub fn create_file_entry(&self, filename: String)
        -> Result<(RootEntry, u16), Box<error::Error>>
    {
        for (index, e) in self.root_entries_all().iter().enumerate() {
            if !e.is_free() { continue; }

            let mut entry = RootEntry::new();
            try!(entry.set_filename(filename));
            return Ok((entry.clone(), index as u16));
        }

        Err(From::from("no free entries"))
    }

    pub fn save_file_entry(&mut self, entry: RootEntry, index: u16)
        -> Result<(), Box<error::Error>>
    {
        let entry_bytes: [u8; BYTES_PER_ROOT_ENTRY];
        unsafe { entry_bytes = mem::transmute(entry); }

        self.root_dir[
            index as usize * BYTES_PER_ROOT_ENTRY ..
            (index as usize + 1) * BYTES_PER_ROOT_ENTRY
        ].clone_from_slice(&entry_bytes[..]);
        Ok(())
    }

    pub fn fat_entries<'a>(&'a self)
        -> Box<Iterator<Item=(usize, u16)> + 'a>
    {
        Box::new(
            self.fat_1
            .windows(2)
            .enumerate()
            .filter_map(|(i, w)|
                if i % 3 == 2 {
                    None
                } else {
                    let val = if i % 3 == 0 {
                        ((w[1] as u16 & 0xf) << 8) | w[0] as u16
                    } else {
                        (w[1] as u16 & 0xf0) | ((w[1] as u16) << 4)
                    };
                    Some((i, val))
                }
            )
        )
    }

    pub fn get_fat_entry(&self, cluster_num: u16) -> u16 {
        let offset: usize = cluster_num as usize * 3 / 2;
        let byte_1: u16 = self.fat_1[offset] as u16;
        let byte_2: u16 = self.fat_1[offset + 1] as u16;

        if cluster_num % 2 == 0 { byte_1 | ((byte_2 & 0x0f) << 8) }
        else                    { (byte_1 >> 4) | (byte_2 << 4) }
    }

    pub fn get_free_fat_entry(&self) -> Option<usize> {
        self.fat_entries()
            .filter(|&(_, e)| e == 0)
            .map(|(i, _)| i + 2)
            .nth(0)
    }

    pub fn write_data_sector(&mut self, sector: usize, data: &[u8])
        -> Result<(), Box<error::Error>>
    {
        let sector = sector - 2;
        let bytes = sector * self.bpb_data.bytes_per_sector as usize;
        if bytes >= self.data_area.len() {
            return Err(From::from(format!("sector {} too high to write to", sector)))
        }

        let mut target_slice = &mut self.data_area[
            self.bpb_data.bytes_per_sector as usize * sector ..
            self.bpb_data.bytes_per_sector as usize * (sector + 1)
        ];

        target_slice.copy_from_slice(data);
        Ok(())
    }
}
<|endoftext|>


<commit_before>//! Filtering and sorting the list of files before displaying them.

use std::cmp::Ordering;
use std::iter::FromIterator;
use std::os::unix::fs::MetadataExt;
use std::path::Path;

use glob;
use natord;

use fs::File;
use fs::DotFilter;

/// The **file filter** processes a list of files before displaying them to
/// the user, by removing files they don’t want to see, and putting the list
/// in the desired order.
///
/// Usually a user does not want to see *every* file in the list. The most
/// common case is to remove files starting with `.`, which are designated
/// as ‘hidden’ files.
///
/// The special files `.` and `..` files are not actually filtered out, but
/// need to be inserted into the list, in a special case.
///
/// The filter also governs sorting the list. After being filtered, pairs of
/// files are compared and sorted based on the result, with the sort field
/// performing the comparison.
#[derive(PartialEq, Debug, Clone)]
pub struct FileFilter {

    /// Whether directories should be listed first, and other types of file
    /// second. Some users prefer it like this.
    pub list_dirs_first: bool,

    /// The metadata field to sort by.
    pub sort_field: SortField,

    /// Whether to reverse the sorting order. This would sort the largest
    /// files first, or files starting with Z, or the most-recently-changed
    /// ones, depending on the sort field.
    pub reverse: bool,

    /// Whether to only show directories
    pub only_dirs: bool,

    /// Which invisible “dot” files to include when listing a directory.
    ///
    /// Files starting with a single “.” are used to determine “system” or
    /// “configuration” files that should not be displayed in a regular
    /// directory listing, and the directory entries “.” and “..” are
    /// considered extra-special.
    ///
    /// This came about more or less by a complete historical accident,
    /// when the original `ls` tried to hide `.` and `..`:
    /// https://plus.google.com/+RobPikeTheHuman/posts/R58WgWwN9jp
    ///
    ///   When one typed ls, however, these files appeared, so either Ken or
    ///   Dennis added a simple test to the program. It was in assembler then,
    ///   but the code in question was equivalent to something like this:
    ///      if (name[0] == '.') continue;
    ///   This statement was a little shorter than what it should have been,
    ///   which is:
    ///      if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
    ///   but hey, it was easy.
    ///
    ///   Two things resulted.
    ///
    ///   First, a bad precedent was set. A lot of other lazy programmers
    ///   introduced bugs by making the same simplification. Actual files
    ///   beginning with periods are often skipped when they should be counted.
    ///
    ///   Second, and much worse, the idea of a "hidden" or "dot" file was
    ///   created. As a consequence, more lazy programmers started dropping
    ///   files into everyone's home directory. I don't have all that much
    ///   stuff installed on the machine I'm using to type this, but my home
    ///   directory has about a hundred dot files and I don't even know what
    ///   most of them are or whether they're still needed. Every file name
    ///   evaluation that goes through my home directory is slowed down by
    ///   this accumulated sludge.
    pub dot_filter: DotFilter,

    /// Glob patterns to ignore. Any file name that matches *any* of these
    /// patterns won’t be displayed in the list.
    pub ignore_patterns: IgnorePatterns,

    /// Whether to ignore Git-ignored patterns.
    /// This is implemented completely separately from the actual Git
    /// repository scanning — a `.gitignore` file will still be scanned even
    /// if there’s no `.git` folder present.
    pub git_ignore: GitIgnore,
}

impl FileFilter {
    /// Remove every file in the given vector that does *not* pass the
    /// filter predicate for files found inside a directory.
    pub fn filter_child_files(&self, files: &mut Vec<File>) {
        files.retain(|f| !self.ignore_patterns.is_ignored(&f.name));

        if self.only_dirs {
            files.retain(|f| f.is_directory());
        }
    }

    /// Remove every file in the given vector that does *not* pass the
    /// filter predicate for file names specified on the command-line.
    ///
    /// The rules are different for these types of files than the other
    /// type because the ignore rules can be used with globbing. For
    /// example, running `exa -I='*.tmp' .vimrc` shouldn’t filter out the
    /// dotfile, because it’s been directly specified. But running
    /// `exa -I='*.ogg' music/*` should filter out the ogg files obtained
    /// from the glob, even though the globbing is done by the shell!
    pub fn filter_argument_files(&self, files: &mut Vec<File>) {
        files.retain(|f| !self.ignore_patterns.is_ignored(&f.name));
    }

    /// Sort the files in the given vector based on the sort field option.
    pub fn sort_files<'a, F>(&self, files: &mut Vec<F>)
    where F: AsRef<File<'a>> {

        files.sort_by(|a, b| self.sort_field.compare_files(a.as_ref(), b.as_ref()));

        if self.reverse {
            files.reverse();
        }

        if self.list_dirs_first {
            // This relies on the fact that `sort_by` is *stable*: it will keep
            // adjacent elements next to each other.
            files.sort_by(|a, b| b.as_ref().is_directory().cmp(&a.as_ref().is_directory()));
        }
    }
}

/// User-supplied field to sort by.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum SortField {

    /// Don’t apply any sorting. This is usually used as an optimisation in
    /// scripts, where the order doesn’t matter.
    Unsorted,

    /// The file name. This is the default sorting.
    Name(SortCase),

    /// The file’s extension, with extensionless files being listed first.
    Extension(SortCase),

    /// The file’s size, in bytes.
    Size,

    /// The file’s inode, which usually corresponds to the order in which
    /// files were created on the filesystem, more or less.
    FileInode,

    /// The time the file was modified (the “mtime”).
    ///
    /// As this is stored as a Unix timestamp, rather than a local time
    /// instance, the time zone does not matter and will only be used to
    /// display the timestamps, not compare them.
    ModifiedDate,

    /// The time the file was accessed (the “atime”).
    ///
    /// Oddly enough, this field rarely holds the *actual* accessed time.
    /// Recording a read time means writing to the file each time it’s read
    /// slows the whole operation down, so many systems will only update the
    /// timestamp in certain circumstances. This has become common enough that
    /// it’s now expected behaviour!
    /// http://unix.stackexchange.com/a/8842
    AccessedDate,

    /// The time the file was changed or created (the “ctime”).
    ///
    /// Contrary to the name, this field is used to mark the time when a
    /// file’s metadata changed -- its permissions, owners, or link count.
    ///
    /// In original Unix, this was, however, meant as creation time.
    /// https://www.bell-labs.com/usr/dmr/www/cacm.html
    CreatedDate,

    /// The type of the file: directories, links, pipes, regular, files, etc.
    ///
    /// Files are ordered according to the `PartialOrd` implementation of
    /// `fs::fields::Type`, so changing that will change this.
    FileType,

    /// The “age” of the file, which is the time it was modified sorted
    /// backwards. The reverse of the `ModifiedDate` ordering!
    ///
    /// It turns out that listing the most-recently-modified files first is a
    /// common-enough use case that it deserves its own variant. This would be
    /// implemented by just using the modified date and setting the reverse
    /// flag, but this would make reversing *that* output not work, which is
    /// bad, even though that’s kind of nonsensical. So it’s its own variant
    /// that can be reversed like usual.
    ModifiedAge,

    /// The file's name, however if the name of the file begins with `.`
    /// ignore the leading `.` and then sort as Name
    NameMixHidden(SortCase),
}

/// Whether a field should be sorted case-sensitively or case-insensitively.
/// This determines which of the `natord` functions to use.
///
/// I kept on forgetting which one was sensitive and which one was
/// insensitive. Would a case-sensitive sort put capital letters first because
/// it takes the case of the letters into account, or intermingle them with
/// lowercase letters because it takes the difference between the two cases
/// into account? I gave up and just named these two variants after the
/// effects they have.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum SortCase {

    /// Sort files case-sensitively with uppercase first, with ‘A’ coming
    /// before ‘a’.
    ABCabc,

    /// Sort files case-insensitively, with ‘A’ being equal to ‘a’.
    AaBbCc,
}

impl SortField {

    /// Compares two files to determine the order they should be listed in,
    /// depending on the search field.
    ///
    /// The `natord` crate is used here to provide a more *natural* sorting
    /// order than just sorting character-by-character. This splits filenames
    /// into groups between letters and numbers, and then sorts those blocks
    /// together, so `file10` will sort after `file9`, instead of before it
    /// because of the `1`.
    pub fn compare_files(&self, a: &File, b: &File) -> Ordering {
        use self::SortCase::{ABCabc, AaBbCc};

        match *self {
            SortField::Unsorted  => Ordering::Equal,

            SortField::Name(ABCabc)  => natord::compare(&a.name, &b.name),
            SortField::Name(AaBbCc)  => natord::compare_ignore_case(&a.name, &b.name),

            SortField::Size          => a.metadata.len().cmp(&b.metadata.len()),
            SortField::FileInode     => a.metadata.ino().cmp(&b.metadata.ino()),
            SortField::ModifiedDate  => a.metadata.mtime().cmp(&b.metadata.mtime()),
            SortField::AccessedDate  => a.metadata.atime().cmp(&b.metadata.atime()),
            SortField::CreatedDate   => a.metadata.ctime().cmp(&b.metadata.ctime()),
            SortField::ModifiedAge   => b.metadata.mtime().cmp(&a.metadata.mtime()),  // flip b and a

            SortField::FileType => match a.type_char().cmp(&b.type_char()) { // todo: this recomputes
                Ordering::Equal  => natord::compare(&*a.name, &*b.name),
                order            => order,
            },

            SortField::Extension(ABCabc) => match a.ext.cmp(&b.ext) {
                Ordering::Equal  => natord::compare(&*a.name, &*b.name),
                order            => order,
            },

            SortField::Extension(AaBbCc) => match a.ext.cmp(&b.ext) {
                Ordering::Equal  => natord::compare_ignore_case(&*a.name, &*b.name),
                order            => order,
            },

            SortField::NameMixHidden(ABCabc) => natord::compare(
                SortField::strip_dot(&a.name),
                SortField::strip_dot(&b.name)
            ),
            SortField::NameMixHidden(AaBbCc) => natord::compare_ignore_case(
                SortField::strip_dot(&a.name),
                SortField::strip_dot(&b.name)
            )
        }
    }

    fn strip_dot(n: &str) -> &str {
        if n.starts_with(".") {
            &n[1..]
        } else {
            n
        }
    }
}

/// The **ignore patterns** are a list of globs that are tested against
/// each filename, and if any of them match, that file isn’t displayed.
/// This lets a user hide, say, text files by ignoring `*.txt`.
#[derive(PartialEq, Default, Debug, Clone)]
pub struct IgnorePatterns {
    patterns: Vec<glob::Pattern>,
}

impl FromIterator<glob::Pattern> for IgnorePatterns {
    fn from_iter<I: IntoIterator<Item = glob::Pattern>>(iter: I) -> Self {
        IgnorePatterns { patterns: iter.into_iter().collect() }
    }
}

impl IgnorePatterns {

    /// Create a new list from the input glob strings, turning the inputs that
    /// are valid glob patterns into an IgnorePatterns. The inputs that don’t
    /// parse correctly are returned separately.
    pub fn parse_from_iter<'a, I: IntoIterator<Item = &'a str>>(iter: I) -> (Self, Vec<glob::PatternError>) {
        let iter = iter.into_iter();

        // Almost all glob patterns are valid, so it’s worth pre-allocating
        // the vector with enough space for all of them.
        let mut patterns = match iter.size_hint() {
            (_, Some(count))  => Vec::with_capacity(count),
             _                => Vec::new(),
        };

        // Similarly, assume there won’t be any errors.
        let mut errors = Vec::new();

        for input in iter {
            match glob::Pattern::new(input) {
                Ok(pat) => patterns.push(pat),
                Err(e)  => errors.push(e),
            }
        }

        (IgnorePatterns { patterns }, errors)
    }

    /// Create a new empty set of patterns that matches nothing.
    pub fn empty() -> IgnorePatterns {
        IgnorePatterns { patterns: Vec::new() }
    }

    /// Test whether the given file should be hidden from the results.
    fn is_ignored(&self, file: &str) -> bool {
        self.patterns.iter().any(|p| p.matches(file))
    }

    /// Test whether the given file should be hidden from the results.
    pub fn is_ignored_path(&self, file: &Path) -> bool {
        self.patterns.iter().any(|p| p.matches_path(file))
    }

    // TODO(ogham): The fact that `is_ignored_path` is pub while `is_ignored`
    // isn’t probably means it’s in the wrong place
}

/// Whether to ignore or display files that are mentioned in `.gitignore` files.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum GitIgnore {

    /// Ignore files that Git would ignore. This means doing a check for a
    /// `.gitignore` file, possibly recursively up the filesystem tree.
    CheckAndIgnore,

    /// Display files, even if Git would ignore them.
    Off,
}

// This is not fully baked yet. The `ignore` crate lists a lot more files that
// we aren’t checking:
//
// > By default, all ignore files found are respected. This includes .ignore,
// > .gitignore, .git/info/exclude and even your global gitignore globs,
// > usually found in $XDG_CONFIG_HOME/git/ignore.

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

    #[test]
    fn empty_matches_nothing() {
        let pats = IgnorePatterns::empty();
        assert_eq!(false, pats.is_ignored("nothing"));
        assert_eq!(false, pats.is_ignored("test.mp3"));
    }

    #[test]
    fn ignores_a_glob() {
        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "*.mp3" ]);
        assert!(fails.is_empty());
        assert_eq!(false, pats.is_ignored("nothing"));
        assert_eq!(true,  pats.is_ignored("test.mp3"));
    }

    #[test]
    fn ignores_an_exact_filename() {
        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing" ]);
        assert!(fails.is_empty());
        assert_eq!(true,  pats.is_ignored("nothing"));
        assert_eq!(false, pats.is_ignored("test.mp3"));
    }

    #[test]
    fn ignores_both() {
        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing", "*.mp3" ]);
        assert!(fails.is_empty());
        assert_eq!(true, pats.is_ignored("nothing"));
        assert_eq!(true, pats.is_ignored("test.mp3"));
    }
}
<commit_msg>Add period for consistency<commit_after>//! Filtering and sorting the list of files before displaying them.

use std::cmp::Ordering;
use std::iter::FromIterator;
use std::os::unix::fs::MetadataExt;
use std::path::Path;

use glob;
use natord;

use fs::File;
use fs::DotFilter;

/// The **file filter** processes a list of files before displaying them to
/// the user, by removing files they don’t want to see, and putting the list
/// in the desired order.
///
/// Usually a user does not want to see *every* file in the list. The most
/// common case is to remove files starting with `.`, which are designated
/// as ‘hidden’ files.
///
/// The special files `.` and `..` files are not actually filtered out, but
/// need to be inserted into the list, in a special case.
///
/// The filter also governs sorting the list. After being filtered, pairs of
/// files are compared and sorted based on the result, with the sort field
/// performing the comparison.
#[derive(PartialEq, Debug, Clone)]
pub struct FileFilter {

    /// Whether directories should be listed first, and other types of file
    /// second. Some users prefer it like this.
    pub list_dirs_first: bool,

    /// The metadata field to sort by.
    pub sort_field: SortField,

    /// Whether to reverse the sorting order. This would sort the largest
    /// files first, or files starting with Z, or the most-recently-changed
    /// ones, depending on the sort field.
    pub reverse: bool,

    /// Whether to only show directories.
    pub only_dirs: bool,

    /// Which invisible “dot” files to include when listing a directory.
    ///
    /// Files starting with a single “.” are used to determine “system” or
    /// “configuration” files that should not be displayed in a regular
    /// directory listing, and the directory entries “.” and “..” are
    /// considered extra-special.
    ///
    /// This came about more or less by a complete historical accident,
    /// when the original `ls` tried to hide `.` and `..`:
    /// https://plus.google.com/+RobPikeTheHuman/posts/R58WgWwN9jp
    ///
    ///   When one typed ls, however, these files appeared, so either Ken or
    ///   Dennis added a simple test to the program. It was in assembler then,
    ///   but the code in question was equivalent to something like this:
    ///      if (name[0] == '.') continue;
    ///   This statement was a little shorter than what it should have been,
    ///   which is:
    ///      if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
    ///   but hey, it was easy.
    ///
    ///   Two things resulted.
    ///
    ///   First, a bad precedent was set. A lot of other lazy programmers
    ///   introduced bugs by making the same simplification. Actual files
    ///   beginning with periods are often skipped when they should be counted.
    ///
    ///   Second, and much worse, the idea of a "hidden" or "dot" file was
    ///   created. As a consequence, more lazy programmers started dropping
    ///   files into everyone's home directory. I don't have all that much
    ///   stuff installed on the machine I'm using to type this, but my home
    ///   directory has about a hundred dot files and I don't even know what
    ///   most of them are or whether they're still needed. Every file name
    ///   evaluation that goes through my home directory is slowed down by
    ///   this accumulated sludge.
    pub dot_filter: DotFilter,

    /// Glob patterns to ignore. Any file name that matches *any* of these
    /// patterns won’t be displayed in the list.
    pub ignore_patterns: IgnorePatterns,

    /// Whether to ignore Git-ignored patterns.
    /// This is implemented completely separately from the actual Git
    /// repository scanning — a `.gitignore` file will still be scanned even
    /// if there’s no `.git` folder present.
    pub git_ignore: GitIgnore,
}

impl FileFilter {
    /// Remove every file in the given vector that does *not* pass the
    /// filter predicate for files found inside a directory.
    pub fn filter_child_files(&self, files: &mut Vec<File>) {
        files.retain(|f| !self.ignore_patterns.is_ignored(&f.name));

        if self.only_dirs {
            files.retain(|f| f.is_directory());
        }
    }

    /// Remove every file in the given vector that does *not* pass the
    /// filter predicate for file names specified on the command-line.
    ///
    /// The rules are different for these types of files than the other
    /// type because the ignore rules can be used with globbing. For
    /// example, running `exa -I='*.tmp' .vimrc` shouldn’t filter out the
    /// dotfile, because it’s been directly specified. But running
    /// `exa -I='*.ogg' music/*` should filter out the ogg files obtained
    /// from the glob, even though the globbing is done by the shell!
    pub fn filter_argument_files(&self, files: &mut Vec<File>) {
        files.retain(|f| !self.ignore_patterns.is_ignored(&f.name));
    }

    /// Sort the files in the given vector based on the sort field option.
    pub fn sort_files<'a, F>(&self, files: &mut Vec<F>)
    where F: AsRef<File<'a>> {

        files.sort_by(|a, b| self.sort_field.compare_files(a.as_ref(), b.as_ref()));

        if self.reverse {
            files.reverse();
        }

        if self.list_dirs_first {
            // This relies on the fact that `sort_by` is *stable*: it will keep
            // adjacent elements next to each other.
            files.sort_by(|a, b| b.as_ref().is_directory().cmp(&a.as_ref().is_directory()));
        }
    }
}

/// User-supplied field to sort by.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum SortField {

    /// Don’t apply any sorting. This is usually used as an optimisation in
    /// scripts, where the order doesn’t matter.
    Unsorted,

    /// The file name. This is the default sorting.
    Name(SortCase),

    /// The file’s extension, with extensionless files being listed first.
    Extension(SortCase),

    /// The file’s size, in bytes.
    Size,

    /// The file’s inode, which usually corresponds to the order in which
    /// files were created on the filesystem, more or less.
    FileInode,

    /// The time the file was modified (the “mtime”).
    ///
    /// As this is stored as a Unix timestamp, rather than a local time
    /// instance, the time zone does not matter and will only be used to
    /// display the timestamps, not compare them.
    ModifiedDate,

    /// The time the file was accessed (the “atime”).
    ///
    /// Oddly enough, this field rarely holds the *actual* accessed time.
    /// Recording a read time means writing to the file each time it’s read
    /// slows the whole operation down, so many systems will only update the
    /// timestamp in certain circumstances. This has become common enough that
    /// it’s now expected behaviour!
    /// http://unix.stackexchange.com/a/8842
    AccessedDate,

    /// The time the file was changed or created (the “ctime”).
    ///
    /// Contrary to the name, this field is used to mark the time when a
    /// file’s metadata changed -- its permissions, owners, or link count.
    ///
    /// In original Unix, this was, however, meant as creation time.
    /// https://www.bell-labs.com/usr/dmr/www/cacm.html
    CreatedDate,

    /// The type of the file: directories, links, pipes, regular, files, etc.
    ///
    /// Files are ordered according to the `PartialOrd` implementation of
    /// `fs::fields::Type`, so changing that will change this.
    FileType,

    /// The “age” of the file, which is the time it was modified sorted
    /// backwards. The reverse of the `ModifiedDate` ordering!
    ///
    /// It turns out that listing the most-recently-modified files first is a
    /// common-enough use case that it deserves its own variant. This would be
    /// implemented by just using the modified date and setting the reverse
    /// flag, but this would make reversing *that* output not work, which is
    /// bad, even though that’s kind of nonsensical. So it’s its own variant
    /// that can be reversed like usual.
    ModifiedAge,

    /// The file's name, however if the name of the file begins with `.`
    /// ignore the leading `.` and then sort as Name
    NameMixHidden(SortCase),
}

/// Whether a field should be sorted case-sensitively or case-insensitively.
/// This determines which of the `natord` functions to use.
///
/// I kept on forgetting which one was sensitive and which one was
/// insensitive. Would a case-sensitive sort put capital letters first because
/// it takes the case of the letters into account, or intermingle them with
/// lowercase letters because it takes the difference between the two cases
/// into account? I gave up and just named these two variants after the
/// effects they have.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum SortCase {

    /// Sort files case-sensitively with uppercase first, with ‘A’ coming
    /// before ‘a’.
    ABCabc,

    /// Sort files case-insensitively, with ‘A’ being equal to ‘a’.
    AaBbCc,
}

impl SortField {

    /// Compares two files to determine the order they should be listed in,
    /// depending on the search field.
    ///
    /// The `natord` crate is used here to provide a more *natural* sorting
    /// order than just sorting character-by-character. This splits filenames
    /// into groups between letters and numbers, and then sorts those blocks
    /// together, so `file10` will sort after `file9`, instead of before it
    /// because of the `1`.
    pub fn compare_files(&self, a: &File, b: &File) -> Ordering {
        use self::SortCase::{ABCabc, AaBbCc};

        match *self {
            SortField::Unsorted  => Ordering::Equal,

            SortField::Name(ABCabc)  => natord::compare(&a.name, &b.name),
            SortField::Name(AaBbCc)  => natord::compare_ignore_case(&a.name, &b.name),

            SortField::Size          => a.metadata.len().cmp(&b.metadata.len()),
            SortField::FileInode     => a.metadata.ino().cmp(&b.metadata.ino()),
            SortField::ModifiedDate  => a.metadata.mtime().cmp(&b.metadata.mtime()),
            SortField::AccessedDate  => a.metadata.atime().cmp(&b.metadata.atime()),
            SortField::CreatedDate   => a.metadata.ctime().cmp(&b.metadata.ctime()),
            SortField::ModifiedAge   => b.metadata.mtime().cmp(&a.metadata.mtime()),  // flip b and a

            SortField::FileType => match a.type_char().cmp(&b.type_char()) { // todo: this recomputes
                Ordering::Equal  => natord::compare(&*a.name, &*b.name),
                order            => order,
            },

            SortField::Extension(ABCabc) => match a.ext.cmp(&b.ext) {
                Ordering::Equal  => natord::compare(&*a.name, &*b.name),
                order            => order,
            },

            SortField::Extension(AaBbCc) => match a.ext.cmp(&b.ext) {
                Ordering::Equal  => natord::compare_ignore_case(&*a.name, &*b.name),
                order            => order,
            },

            SortField::NameMixHidden(ABCabc) => natord::compare(
                SortField::strip_dot(&a.name),
                SortField::strip_dot(&b.name)
            ),
            SortField::NameMixHidden(AaBbCc) => natord::compare_ignore_case(
                SortField::strip_dot(&a.name),
                SortField::strip_dot(&b.name)
            )
        }
    }

    fn strip_dot(n: &str) -> &str {
        if n.starts_with(".") {
            &n[1..]
        } else {
            n
        }
    }
}

/// The **ignore patterns** are a list of globs that are tested against
/// each filename, and if any of them match, that file isn’t displayed.
/// This lets a user hide, say, text files by ignoring `*.txt`.
#[derive(PartialEq, Default, Debug, Clone)]
pub struct IgnorePatterns {
    patterns: Vec<glob::Pattern>,
}

impl FromIterator<glob::Pattern> for IgnorePatterns {
    fn from_iter<I: IntoIterator<Item = glob::Pattern>>(iter: I) -> Self {
        IgnorePatterns { patterns: iter.into_iter().collect() }
    }
}

impl IgnorePatterns {

    /// Create a new list from the input glob strings, turning the inputs that
    /// are valid glob patterns into an IgnorePatterns. The inputs that don’t
    /// parse correctly are returned separately.
    pub fn parse_from_iter<'a, I: IntoIterator<Item = &'a str>>(iter: I) -> (Self, Vec<glob::PatternError>) {
        let iter = iter.into_iter();

        // Almost all glob patterns are valid, so it’s worth pre-allocating
        // the vector with enough space for all of them.
        let mut patterns = match iter.size_hint() {
            (_, Some(count))  => Vec::with_capacity(count),
             _                => Vec::new(),
        };

        // Similarly, assume there won’t be any errors.
        let mut errors = Vec::new();

        for input in iter {
            match glob::Pattern::new(input) {
                Ok(pat) => patterns.push(pat),
                Err(e)  => errors.push(e),
            }
        }

        (IgnorePatterns { patterns }, errors)
    }

    /// Create a new empty set of patterns that matches nothing.
    pub fn empty() -> IgnorePatterns {
        IgnorePatterns { patterns: Vec::new() }
    }

    /// Test whether the given file should be hidden from the results.
    fn is_ignored(&self, file: &str) -> bool {
        self.patterns.iter().any(|p| p.matches(file))
    }

    /// Test whether the given file should be hidden from the results.
    pub fn is_ignored_path(&self, file: &Path) -> bool {
        self.patterns.iter().any(|p| p.matches_path(file))
    }

    // TODO(ogham): The fact that `is_ignored_path` is pub while `is_ignored`
    // isn’t probably means it’s in the wrong place
}

/// Whether to ignore or display files that are mentioned in `.gitignore` files.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum GitIgnore {

    /// Ignore files that Git would ignore. This means doing a check for a
    /// `.gitignore` file, possibly recursively up the filesystem tree.
    CheckAndIgnore,

    /// Display files, even if Git would ignore them.
    Off,
}

// This is not fully baked yet. The `ignore` crate lists a lot more files that
// we aren’t checking:
//
// > By default, all ignore files found are respected. This includes .ignore,
// > .gitignore, .git/info/exclude and even your global gitignore globs,
// > usually found in $XDG_CONFIG_HOME/git/ignore.

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

    #[test]
    fn empty_matches_nothing() {
        let pats = IgnorePatterns::empty();
        assert_eq!(false, pats.is_ignored("nothing"));
        assert_eq!(false, pats.is_ignored("test.mp3"));
    }

    #[test]
    fn ignores_a_glob() {
        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "*.mp3" ]);
        assert!(fails.is_empty());
        assert_eq!(false, pats.is_ignored("nothing"));
        assert_eq!(true,  pats.is_ignored("test.mp3"));
    }

    #[test]
    fn ignores_an_exact_filename() {
        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing" ]);
        assert!(fails.is_empty());
        assert_eq!(true,  pats.is_ignored("nothing"));
        assert_eq!(false, pats.is_ignored("test.mp3"));
    }

    #[test]
    fn ignores_both() {
        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing", "*.mp3" ]);
        assert!(fails.is_empty());
        assert_eq!(true, pats.is_ignored("nothing"));
        assert_eq!(true, pats.is_ignored("test.mp3"));
    }
}
<|endoftext|>


<commit_before>// This file is part of libfringe, a low-level green threading library.
// Copyright (c) whitequark <whitequark@whitequark.org>
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

//! Generators.
//!
//! Generators allow repeatedly suspending the execution of a function,
//! returning a value to the caller, and resuming the suspended function
//! afterwards.

use core::marker::PhantomData;
use core::{ptr, mem};
use core::cell::Cell;

use stack;
use debug;
use arch::{self, StackPointer};

#[derive(Debug, Clone, Copy)]
pub enum State {
  /// Generator can be resumed. This is the initial state.
  Runnable,
  /// Generator cannot be resumed. This is the state of the generator after
  /// the generator function has returned or panicked.
  Unavailable
}

/// Generator wraps a function and allows suspending its execution more than once, returning
/// a value each time.
///
/// The first time `resume(input0)` is called, the function is called as `f(yielder, input0)`.
/// It runs until it suspends its execution through `yielder.suspend(output0)`, after which
/// `resume(input0)` returns `output0`. The function can be resumed again using `resume(input1)`,
/// after which `yielder.suspend(output0)` returns `input1`, and so on. Once the function returns,
/// the `resume()` call will return `None`, and it will return `None` every time it is called
/// after that.
///
/// If the generator function panics, the panic is propagated through the `resume()` call as usual.
///
/// After the generator function returns or panics, it is safe to reclaim the generator stack
/// using `unwrap()`.
///
/// `state()` can be used to determine whether the generator function has returned;
/// the state is `State::Runnable` after creation and suspension, and `State::Unavailable`
/// once the generator function returns or panics.
///
/// When the input type is `()`, a generator implements the Iterator trait.
///
/// # Example
///
/// ```
/// use fringe::{OsStack, Generator};
///
/// let stack = OsStack::new(0).unwrap();
/// let mut add_one = Generator::new(stack, move |yielder, mut input| {
///   loop {
///     if input == 0 { break }
///     input = yielder.suspend(input + 1)
///   }
/// });
/// println!("{:?}", add_one.resume(2)); // prints Some(3)
/// println!("{:?}", add_one.resume(3)); // prints Some(4)
/// println!("{:?}", add_one.resume(0)); // prints None
/// ```
///
/// # Iterator example
///
/// ```
/// use fringe::{OsStack, Generator};
///
/// let stack = OsStack::new(0).unwrap();
/// let mut nat = Generator::new(stack, move |yielder, ()| {
///   for i in 1.. { yielder.suspend(i) }
/// });
/// println!("{:?}", nat.next()); // prints Some(0)
/// println!("{:?}", nat.next()); // prints Some(1)
/// println!("{:?}", nat.next()); // prints Some(2)
/// ```
#[derive(Debug)]
pub struct Generator<Input: Send, Output: Send, Stack: stack::Stack> {
  state:     State,
  stack:     Stack,
  stack_id:  debug::StackId,
  stack_ptr: arch::StackPointer,
  phantom:   (PhantomData<*const Input>, PhantomData<*const Output>)
}

impl<Input, Output, Stack> Generator<Input, Output, Stack>
    where Input: Send, Output: Send, Stack: stack::Stack {
  /// Creates a new generator.
  ///
  /// See also the [contract](../trait.GuardedStack.html) that needs to be fulfilled by `stack`.
  pub fn new<F>(stack: Stack, f: F) -> Generator<Input, Output, Stack>
      where Stack: stack::GuardedStack,
            F: FnOnce(&mut Yielder<Input, Output>, Input) + Send {
    unsafe { Generator::unsafe_new(stack, f) }
  }

  /// Same as `new`, but does not require `stack` to have a guard page.
  ///
  /// This function is unsafe because the generator function can easily violate
  /// memory safety by overflowing the stack. It is useful in environments where
  /// guarded stacks do not exist, e.g. in absence of an MMU.
  ///
  /// See also the [contract](../trait.Stack.html) that needs to be fulfilled by `stack`.
  pub unsafe fn unsafe_new<F>(stack: Stack, f: F) -> Generator<Input, Output, Stack>
      where F: FnOnce(&mut Yielder<Input, Output>, Input) + Send {
    unsafe extern "C" fn generator_wrapper<Input, Output, Stack, F>(env: usize, stack_ptr: StackPointer) -> !
        where Input: Send, Output: Send, Stack: stack::Stack,
              F: FnOnce(&mut Yielder<Input, Output>, Input) {
      // Retrieve our environment from the callee and return control to it.
      let f = ptr::read(env as *const F);
      let (data, stack_ptr) = arch::swap(0, stack_ptr, None);
      // See the second half of Yielder::suspend_bare.
      let input = ptr::read(data as *const Input);
      // Run the body of the generator.
      let mut yielder = Yielder::new(stack_ptr);
      f(&mut yielder, input);
      // Past this point, the generator has dropped everything it has held.
      loop { yielder.suspend_bare(None); }
    }

    let stack_id  = debug::StackId::register(&stack);
    let stack_ptr = arch::init(&stack, generator_wrapper::<Input, Output, Stack, F>);

    // Transfer environment to the callee.
    let stack_ptr = arch::swap(&f as *const F as usize, stack_ptr, Some(&stack)).1;
    mem::forget(f);

    Generator {
      state:     State::Runnable,
      stack:     stack,
      stack_id:  stack_id,
      stack_ptr: stack_ptr,
      phantom:   (PhantomData, PhantomData)
    }
  }

  /// Resumes the generator and return the next value it yields.
  /// If the generator function has returned, returns `None`.
  #[inline]
  pub fn resume(&mut self, input: Input) -> Option<Output> {
    match self.state {
      State::Runnable => {
        // Set the state to Unavailable. Since we have exclusive access to the generator,
        // the only case where this matters is the generator function panics, after which
        // it must not be invocable again.
        self.state = State::Unavailable;

        // Switch to the generator function, and retrieve the yielded value.
        let val = unsafe {
          let (data_out, stack_ptr) = arch::swap(&input as *const Input as usize, self.stack_ptr, Some(&self.stack));
          self.stack_ptr = stack_ptr;
          mem::forget(input);
          ptr::read(data_out as *const Option<Output>)
        };

        // Unless the generator function has returned, it can be switched to again, so
        // set the state to Runnable.
        if val.is_some() { self.state = State::Runnable }

        val
      }
      State::Unavailable => None
    }
  }

  /// Returns the state of the generator.
  #[inline]
  pub fn state(&self) -> State { self.state }

  /// Extracts the stack from a generator when the generator function has returned.
  /// If the generator function has not returned
  /// (i.e. `self.state() == State::Runnable`), panics.
  pub fn unwrap(self) -> Stack {
    match self.state {
      State::Runnable    => panic!("Argh! Bastard! Don't touch that!"),
      State::Unavailable => self.stack
    }
  }
}

/// Yielder is an interface provided to every generator through which it
/// returns a value.
#[derive(Debug)]
pub struct Yielder<Input: Send, Output: Send> {
  stack_ptr: Cell<StackPointer>,
  phantom: (PhantomData<*const Input>, PhantomData<*const Output>)
}

impl<Input, Output> Yielder<Input, Output>
    where Input: Send, Output: Send {
  fn new(stack_ptr: StackPointer) -> Yielder<Input, Output> {
    Yielder {
      stack_ptr: Cell::new(stack_ptr),
      phantom: (PhantomData, PhantomData)
    }
  }

  #[inline(always)]
  fn suspend_bare(&self, val: Option<Output>) -> Input {
    unsafe {
      let (data, stack_ptr) = arch::swap(&val as *const Option<Output> as usize, self.stack_ptr.get(), None);
      self.stack_ptr.set(stack_ptr);
      mem::forget(val);
      ptr::read(data as *const Input)
    }
  }

  /// Suspends the generator and returns `Some(item)` from the `resume()`
  /// invocation that resumed the generator.
  #[inline(always)]
  pub fn suspend(&self, item: Output) -> Input {
    self.suspend_bare(Some(item))
  }
}

impl<Output, Stack> Iterator for Generator<(), Output, Stack>
    where Output: Send, Stack: stack::Stack {
  type Item = Output;

  fn next(&mut self) -> Option<Self::Item> { self.resume(()) }
}
<commit_msg>Use PhantomData-of-tuple instead of tuple-of-PhantomData<commit_after>// This file is part of libfringe, a low-level green threading library.
// Copyright (c) whitequark <whitequark@whitequark.org>
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

//! Generators.
//!
//! Generators allow repeatedly suspending the execution of a function,
//! returning a value to the caller, and resuming the suspended function
//! afterwards.

use core::marker::PhantomData;
use core::{ptr, mem};
use core::cell::Cell;

use stack;
use debug;
use arch::{self, StackPointer};

#[derive(Debug, Clone, Copy)]
pub enum State {
  /// Generator can be resumed. This is the initial state.
  Runnable,
  /// Generator cannot be resumed. This is the state of the generator after
  /// the generator function has returned or panicked.
  Unavailable
}

/// Generator wraps a function and allows suspending its execution more than once, returning
/// a value each time.
///
/// The first time `resume(input0)` is called, the function is called as `f(yielder, input0)`.
/// It runs until it suspends its execution through `yielder.suspend(output0)`, after which
/// `resume(input0)` returns `output0`. The function can be resumed again using `resume(input1)`,
/// after which `yielder.suspend(output0)` returns `input1`, and so on. Once the function returns,
/// the `resume()` call will return `None`, and it will return `None` every time it is called
/// after that.
///
/// If the generator function panics, the panic is propagated through the `resume()` call as usual.
///
/// After the generator function returns or panics, it is safe to reclaim the generator stack
/// using `unwrap()`.
///
/// `state()` can be used to determine whether the generator function has returned;
/// the state is `State::Runnable` after creation and suspension, and `State::Unavailable`
/// once the generator function returns or panics.
///
/// When the input type is `()`, a generator implements the Iterator trait.
///
/// # Example
///
/// ```
/// use fringe::{OsStack, Generator};
///
/// let stack = OsStack::new(0).unwrap();
/// let mut add_one = Generator::new(stack, move |yielder, mut input| {
///   loop {
///     if input == 0 { break }
///     input = yielder.suspend(input + 1)
///   }
/// });
/// println!("{:?}", add_one.resume(2)); // prints Some(3)
/// println!("{:?}", add_one.resume(3)); // prints Some(4)
/// println!("{:?}", add_one.resume(0)); // prints None
/// ```
///
/// # Iterator example
///
/// ```
/// use fringe::{OsStack, Generator};
///
/// let stack = OsStack::new(0).unwrap();
/// let mut nat = Generator::new(stack, move |yielder, ()| {
///   for i in 1.. { yielder.suspend(i) }
/// });
/// println!("{:?}", nat.next()); // prints Some(0)
/// println!("{:?}", nat.next()); // prints Some(1)
/// println!("{:?}", nat.next()); // prints Some(2)
/// ```
#[derive(Debug)]
pub struct Generator<Input: Send, Output: Send, Stack: stack::Stack> {
  state:     State,
  stack:     Stack,
  stack_id:  debug::StackId,
  stack_ptr: arch::StackPointer,
  phantom:   PhantomData<(*const Input, *const Output)>
}

impl<Input, Output, Stack> Generator<Input, Output, Stack>
    where Input: Send, Output: Send, Stack: stack::Stack {
  /// Creates a new generator.
  ///
  /// See also the [contract](../trait.GuardedStack.html) that needs to be fulfilled by `stack`.
  pub fn new<F>(stack: Stack, f: F) -> Generator<Input, Output, Stack>
      where Stack: stack::GuardedStack,
            F: FnOnce(&mut Yielder<Input, Output>, Input) + Send {
    unsafe { Generator::unsafe_new(stack, f) }
  }

  /// Same as `new`, but does not require `stack` to have a guard page.
  ///
  /// This function is unsafe because the generator function can easily violate
  /// memory safety by overflowing the stack. It is useful in environments where
  /// guarded stacks do not exist, e.g. in absence of an MMU.
  ///
  /// See also the [contract](../trait.Stack.html) that needs to be fulfilled by `stack`.
  pub unsafe fn unsafe_new<F>(stack: Stack, f: F) -> Generator<Input, Output, Stack>
      where F: FnOnce(&mut Yielder<Input, Output>, Input) + Send {
    unsafe extern "C" fn generator_wrapper<Input, Output, Stack, F>(env: usize, stack_ptr: StackPointer) -> !
        where Input: Send, Output: Send, Stack: stack::Stack,
              F: FnOnce(&mut Yielder<Input, Output>, Input) {
      // Retrieve our environment from the callee and return control to it.
      let f = ptr::read(env as *const F);
      let (data, stack_ptr) = arch::swap(0, stack_ptr, None);
      // See the second half of Yielder::suspend_bare.
      let input = ptr::read(data as *const Input);
      // Run the body of the generator.
      let mut yielder = Yielder::new(stack_ptr);
      f(&mut yielder, input);
      // Past this point, the generator has dropped everything it has held.
      loop { yielder.suspend_bare(None); }
    }

    let stack_id  = debug::StackId::register(&stack);
    let stack_ptr = arch::init(&stack, generator_wrapper::<Input, Output, Stack, F>);

    // Transfer environment to the callee.
    let stack_ptr = arch::swap(&f as *const F as usize, stack_ptr, Some(&stack)).1;
    mem::forget(f);

    Generator {
      state:     State::Runnable,
      stack:     stack,
      stack_id:  stack_id,
      stack_ptr: stack_ptr,
      phantom:   PhantomData
    }
  }

  /// Resumes the generator and return the next value it yields.
  /// If the generator function has returned, returns `None`.
  #[inline]
  pub fn resume(&mut self, input: Input) -> Option<Output> {
    match self.state {
      State::Runnable => {
        // Set the state to Unavailable. Since we have exclusive access to the generator,
        // the only case where this matters is the generator function panics, after which
        // it must not be invocable again.
        self.state = State::Unavailable;

        // Switch to the generator function, and retrieve the yielded value.
        let val = unsafe {
          let (data_out, stack_ptr) = arch::swap(&input as *const Input as usize, self.stack_ptr, Some(&self.stack));
          self.stack_ptr = stack_ptr;
          mem::forget(input);
          ptr::read(data_out as *const Option<Output>)
        };

        // Unless the generator function has returned, it can be switched to again, so
        // set the state to Runnable.
        if val.is_some() { self.state = State::Runnable }

        val
      }
      State::Unavailable => None
    }
  }

  /// Returns the state of the generator.
  #[inline]
  pub fn state(&self) -> State { self.state }

  /// Extracts the stack from a generator when the generator function has returned.
  /// If the generator function has not returned
  /// (i.e. `self.state() == State::Runnable`), panics.
  pub fn unwrap(self) -> Stack {
    match self.state {
      State::Runnable    => panic!("Argh! Bastard! Don't touch that!"),
      State::Unavailable => self.stack
    }
  }
}

/// Yielder is an interface provided to every generator through which it
/// returns a value.
#[derive(Debug)]
pub struct Yielder<Input: Send, Output: Send> {
  stack_ptr: Cell<StackPointer>,
  phantom: PhantomData<(*const Input, *const Output)>
}

impl<Input, Output> Yielder<Input, Output>
    where Input: Send, Output: Send {
  fn new(stack_ptr: StackPointer) -> Yielder<Input, Output> {
    Yielder {
      stack_ptr: Cell::new(stack_ptr),
      phantom: PhantomData
    }
  }

  #[inline(always)]
  fn suspend_bare(&self, val: Option<Output>) -> Input {
    unsafe {
      let (data, stack_ptr) = arch::swap(&val as *const Option<Output> as usize, self.stack_ptr.get(), None);
      self.stack_ptr.set(stack_ptr);
      mem::forget(val);
      ptr::read(data as *const Input)
    }
  }

  /// Suspends the generator and returns `Some(item)` from the `resume()`
  /// invocation that resumed the generator.
  #[inline(always)]
  pub fn suspend(&self, item: Output) -> Input {
    self.suspend_bare(Some(item))
  }
}

impl<Output, Stack> Iterator for Generator<(), Output, Stack>
    where Output: Send, Stack: stack::Stack {
  type Item = Output;

  fn next(&mut self) -> Option<Self::Item> { self.resume(()) }
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate imageflow_server;
#[macro_use] extern crate lazy_static;
extern crate regex;

use regex::Regex;

use clap::{App, Arg, SubCommand};
use ::imageflow_server::preludes::*;
use ::std::path::{Path, PathBuf};

use std::net::ToSocketAddrs;

extern crate imageflow_types as s;

fn main() {
    let exit_code = main_with_exit_code();
    std::process::exit(exit_code);
}

fn parse_mount(s: &str) -> std::result::Result<MountLocation, String>{
    //.split permits empty elements. We just join consecutive empty elements to allow escaping of : via ::
    let mut parts = s.split(":").fold((Vec::new(),false), | (mut list, previous_empty), item| {
        if previous_empty && item.is_empty(){
            (list, false)
        }else if item.is_empty(){
            list.push(item.to_owned());
            (list, true)
        }else{
            list.push(item.to_owned());
            (list, false)
        }
    }).0;

    if parts.len() < 2 {
        Err(format!("--mount prefix:engine:args  Mount value must contain at least prefix:engine - received {:?} ({:?})", s, &parts))
    }else{
        MountLocation::parse(parts.remove(0), parts.remove(0), parts)
    }
}

fn main_with_exit_code() -> i32 {
    let version = s::version::one_line_version();
    let app = App::new("imageflow_server").version(version.as_ref())
        .subcommand(
            SubCommand::with_name("diagnose")
                .about("Diagnostic utilities")
                .arg(
                    Arg::with_name("show-compilation-info").long("show-compilation-info")
                        .help("Show all the information stored in this executable about the environment in which it was compiled.")
                ).arg(
                Arg::with_name("call-panic").long("call-panic")
                    .help("Triggers a Rust panic (so you can observe failure/backtrace behavior)")
            )
        )
        .subcommand(
            SubCommand::with_name("start")
                .about("Start HTTP server")
                .arg(
                    Arg::with_name("mount").long("mount").takes_value(true).empty_values(false).multiple(true).required_unless("demo")
                        .validator(|f| parse_mount(&f).map(|r| ()))
                        .help("Serve images from the given location using the provided API, e.g --mount \"/prefix/:ir4_local:./{}\" --mount \"/extern/:ir4_http:http:://domain.com/{}\"\n Escape colons by doubling, e.g. http:// -> http:://")
                ).arg(Arg::with_name("demo").long("demo").conflicts_with("mount")
                .help("Start demo server (on localhost:39876 by default) with mounts /ir4/proxy/unsplash -> http://images.unsplash.com/"))

                .arg(Arg::with_name("bind-address").long("bind-address").takes_value(true).required(false).default_value("localhost")
                    .help("The IPv4 or IPv6 address to bind to (or the hostname, like localhost). 0.0.0.0 binds to all addresses."
                ))
                .arg(Arg::with_name("port").long("port").short("-p").takes_value(true).default_value("39876").required(false).help("Set the port that the server will listen on"))

                .arg(Arg::with_name("data-dir").long("data-dir").takes_value(true).required_unless("demo")
                    .validator(|f| if Path::new(&f).is_dir() { Ok(()) } else { Err(format!("The specified data-dir {} must be an existing directory. ", f)) })
                .help("An existing directory for logging and caching"))
                .arg(Arg::with_name("integration-test").long("integration-test").hidden(true).help("Never use this outside of an integration test. Exposes an HTTP endpoint to kill the server."))

        );

    let matches = app.get_matches();

    if let Some(ref matches) = matches.subcommand_matches("diagnose") {
        let m: &&clap::ArgMatches = matches;

        if m.is_present("show-compilation-info") {
            println!("{}\n{}\n",
                     s::version::one_line_version(),
                     s::version::all_build_info_pairs());
            return 0;
        }
        if m.is_present("call-panic") {
            panic!("Panicking on command");
        }
    }
    if let Some(ref matches) = matches.subcommand_matches("start") {
        let m: &&clap::ArgMatches = matches;

        let port = matches.value_of("port").map(|s| s.parse::<u16>().expect("Port must be a valid 16-bit positive integer") ).unwrap_or(39876);
        let integration_test = matches.is_present("integration-test");
        let data_dir = m.value_of("data-dir").map(|s| PathBuf::from(s));
        let bind = m.value_of("bind-address").map(|s| s.to_owned()).expect("bind address required");

        if bind != "localhost" && bind != "127.0.0.1" && bind != "::1"{
            println!("This build of imageflow_server only permits connections from localhost (address {} rejected). It is not secure.", &bind);
            std::process::exit(64);
        }
        let combined = format!("{}:{}", bind, port);

        {
            let socket_addr_iter = combined.to_socket_addrs();
            if socket_addr_iter.is_err() || socket_addr_iter.unwrap().next().is_none() {
                println!("Invalid value for --bind-address. {} failed to parse.", &combined);
                std::process::exit(64);
            }
        }

        if m.is_present("demo"){
            //TODO: fetch an examples directory, with javascript/html/css and images, and mount that

            // If not provided, ./imageflow_data is created and used

            let alt_data_dir = Path::new(".").join("imageflow_data");

            let demo_commit = s::version::get_build_env_value("GIT_COMMIT").unwrap();

            let mut mounts = vec![
            MountLocation {
                engine: MountedEngine::Ir4Http,
                prefix: "/ir4/proxy_unsplash/".to_owned(),
                engine_args: vec!["http://images.unsplash.com/".to_owned()]
            },
            MountLocation {
                engine: MountedEngine::PermacacheProxyGuessContentTypes,
                prefix: "/proxied_demo/".to_owned(),
                engine_args: vec![format!("http://raw.githubusercontent.com/imazen/imageflow/{}/imageflow_server/demo/", demo_commit)]
            },
            MountLocation {
                engine: MountedEngine::Ir4Http,
                prefix: "/demo_images/".to_owned(),
                engine_args: vec!["http://resizer-images.s3.amazonaws.com/".to_owned()]
            }
            ];
            let local_demo_folder = Path::new(env!("CARGO_MANIFEST_DIR")).join("demo");
            if local_demo_folder.exists() {
                mounts.push(MountLocation {
                                    engine: MountedEngine::Static,
                                    prefix: "/src_demo/".to_owned(),
                                    engine_args: vec![local_demo_folder.as_path().to_str().unwrap().to_owned()]
                                });

                println!("Open your browser to http://{}/src_demo/index.html", &combined);
            }else{
                println!("Open your browser to http://{}/proxied_demo/index.html", &combined);

            }

            ::imageflow_server::serve(StartServerConfig {
                bind_addr: combined,
                data_dir: data_dir.unwrap_or_else(|| { if !alt_data_dir.exists() { std::fs::create_dir_all(&alt_data_dir).unwrap(); } alt_data_dir }),
                default_cache_layout: Some(FolderLayout::Tiny),
                integration_test: integration_test,
                mounts: mounts
            });
        }else {
            let mounts = m.values_of_lossy("mount").expect("at least one --mount required").into_iter().map(|s| parse_mount(&s).expect("validator not working - bug in clap?")).collect::<Vec<MountLocation>>();

            ::imageflow_server::serve(StartServerConfig {
                bind_addr: combined,
                data_dir: data_dir.expect("data-dir required"),
                mounts: mounts,
                default_cache_layout: Some(FolderLayout::Normal),
                integration_test: integration_test
            });
        }
        return 0;
    }

    64
}

<commit_msg>imageflow_server: only force localhost for tagged builds<commit_after>extern crate clap;
extern crate imageflow_server;
#[macro_use] extern crate lazy_static;
extern crate regex;

use regex::Regex;

use clap::{App, Arg, SubCommand};
use ::imageflow_server::preludes::*;
use ::std::path::{Path, PathBuf};

use std::net::ToSocketAddrs;

extern crate imageflow_types as s;

fn main() {
    let exit_code = main_with_exit_code();
    std::process::exit(exit_code);
}

fn parse_mount(s: &str) -> std::result::Result<MountLocation, String>{
    //.split permits empty elements. We just join consecutive empty elements to allow escaping of : via ::
    let mut parts = s.split(":").fold((Vec::new(),false), | (mut list, previous_empty), item| {
        if previous_empty && item.is_empty(){
            (list, false)
        }else if item.is_empty(){
            list.push(item.to_owned());
            (list, true)
        }else{
            list.push(item.to_owned());
            (list, false)
        }
    }).0;

    if parts.len() < 2 {
        Err(format!("--mount prefix:engine:args  Mount value must contain at least prefix:engine - received {:?} ({:?})", s, &parts))
    }else{
        MountLocation::parse(parts.remove(0), parts.remove(0), parts)
    }
}

fn main_with_exit_code() -> i32 {
    let version = s::version::one_line_version();
    let app = App::new("imageflow_server").version(version.as_ref())
        .subcommand(
            SubCommand::with_name("diagnose")
                .about("Diagnostic utilities")
                .arg(
                    Arg::with_name("show-compilation-info").long("show-compilation-info")
                        .help("Show all the information stored in this executable about the environment in which it was compiled.")
                ).arg(
                Arg::with_name("call-panic").long("call-panic")
                    .help("Triggers a Rust panic (so you can observe failure/backtrace behavior)")
            )
        )
        .subcommand(
            SubCommand::with_name("start")
                .about("Start HTTP server")
                .arg(
                    Arg::with_name("mount").long("mount").takes_value(true).empty_values(false).multiple(true).required_unless("demo")
                        .validator(|f| parse_mount(&f).map(|r| ()))
                        .help("Serve images from the given location using the provided API, e.g --mount \"/prefix/:ir4_local:./{}\" --mount \"/extern/:ir4_http:http:://domain.com/{}\"\n Escape colons by doubling, e.g. http:// -> http:://")
                ).arg(Arg::with_name("demo").long("demo").conflicts_with("mount")
                .help("Start demo server (on localhost:39876 by default) with mounts /ir4/proxy/unsplash -> http://images.unsplash.com/"))

                .arg(Arg::with_name("bind-address").long("bind-address").takes_value(true).required(false).default_value("localhost")
                    .help("The IPv4 or IPv6 address to bind to (or the hostname, like localhost). 0.0.0.0 binds to all addresses."
                ))
                .arg(Arg::with_name("port").long("port").short("-p").takes_value(true).default_value("39876").required(false).help("Set the port that the server will listen on"))

                .arg(Arg::with_name("data-dir").long("data-dir").takes_value(true).required_unless("demo")
                    .validator(|f| if Path::new(&f).is_dir() { Ok(()) } else { Err(format!("The specified data-dir {} must be an existing directory. ", f)) })
                .help("An existing directory for logging and caching"))
                .arg(Arg::with_name("integration-test").long("integration-test").hidden(true).help("Never use this outside of an integration test. Exposes an HTTP endpoint to kill the server."))

        );

    let matches = app.get_matches();

    if let Some(ref matches) = matches.subcommand_matches("diagnose") {
        let m: &&clap::ArgMatches = matches;

        if m.is_present("show-compilation-info") {
            println!("{}\n{}\n",
                     s::version::one_line_version(),
                     s::version::all_build_info_pairs());
            return 0;
        }
        if m.is_present("call-panic") {
            panic!("Panicking on command");
        }
    }
    if let Some(ref matches) = matches.subcommand_matches("start") {
        let m: &&clap::ArgMatches = matches;

        let port = matches.value_of("port").map(|s| s.parse::<u16>().expect("Port must be a valid 16-bit positive integer") ).unwrap_or(39876);
        let integration_test = matches.is_present("integration-test");
        let data_dir = m.value_of("data-dir").map(|s| PathBuf::from(s));
        let bind = m.value_of("bind-address").map(|s| s.to_owned()).expect("bind address required");

        let is_release = option_env!("GIT_OPTIONAL_TAG").is_some() && !option_env!("GIT_OPTIONAL_TAG").unwrap().is_empty();
        let vars_present = option_env!("GIT_COMMIT").is_some();

        if is_release || !vars_present {
            if bind != "localhost" && bind != "127.0.0.1" && bind != "::1" {
                println!("This build of imageflow_server only permits connections from localhost (address {} rejected). It is not secure.", &bind);
                std::process::exit(64);
            }
        }

        let combined = format!("{}:{}", bind, port);

        {
            let socket_addr_iter = combined.to_socket_addrs();
            if socket_addr_iter.is_err() || socket_addr_iter.unwrap().next().is_none() {
                println!("Invalid value for --bind-address. {} failed to parse.", &combined);
                std::process::exit(64);
            }
        }

        if m.is_present("demo"){
            //TODO: fetch an examples directory, with javascript/html/css and images, and mount that

            // If not provided, ./imageflow_data is created and used

            let alt_data_dir = Path::new(".").join("imageflow_data");

            let demo_commit = s::version::get_build_env_value("GIT_COMMIT").unwrap();

            let mut mounts = vec![
            MountLocation {
                engine: MountedEngine::Ir4Http,
                prefix: "/ir4/proxy_unsplash/".to_owned(),
                engine_args: vec!["http://images.unsplash.com/".to_owned()]
            },
            MountLocation {
                engine: MountedEngine::PermacacheProxyGuessContentTypes,
                prefix: "/proxied_demo/".to_owned(),
                engine_args: vec![format!("http://raw.githubusercontent.com/imazen/imageflow/{}/imageflow_server/demo/", demo_commit)]
            },
            MountLocation {
                engine: MountedEngine::Ir4Http,
                prefix: "/demo_images/".to_owned(),
                engine_args: vec!["http://resizer-images.s3.amazonaws.com/".to_owned()]
            }
            ];
            let local_demo_folder = Path::new(env!("CARGO_MANIFEST_DIR")).join("demo");
            if local_demo_folder.exists() {
                mounts.push(MountLocation {
                                    engine: MountedEngine::Static,
                                    prefix: "/src_demo/".to_owned(),
                                    engine_args: vec![local_demo_folder.as_path().to_str().unwrap().to_owned()]
                                });

                println!("Open your browser to http://{}/src_demo/index.html", &combined);
            }else{
                println!("Open your browser to http://{}/proxied_demo/index.html", &combined);

            }

            ::imageflow_server::serve(StartServerConfig {
                bind_addr: combined,
                data_dir: data_dir.unwrap_or_else(|| { if !alt_data_dir.exists() { std::fs::create_dir_all(&alt_data_dir).unwrap(); } alt_data_dir }),
                default_cache_layout: Some(FolderLayout::Tiny),
                integration_test: integration_test,
                mounts: mounts
            });
        }else {
            let mounts = m.values_of_lossy("mount").expect("at least one --mount required").into_iter().map(|s| parse_mount(&s).expect("validator not working - bug in clap?")).collect::<Vec<MountLocation>>();

            ::imageflow_server::serve(StartServerConfig {
                bind_addr: combined,
                data_dir: data_dir.expect("data-dir required"),
                mounts: mounts,
                default_cache_layout: Some(FolderLayout::Normal),
                integration_test: integration_test
            });
        }
        return 0;
    }

    64
}

<|endoftext|>