<commit_before>extern crate clap;
extern crate goblin;
extern crate rustc_demangle;
extern crate toml;
extern crate capstone;
#[macro_use] extern crate quick_error;

pub mod symbol;

use clap::{Arg, App, SubCommand, AppSettings};
use symbol::Symbol;

use std::fs::File;
use std::io::{self, Cursor, Read, Seek, ErrorKind};
use std::io::SeekFrom;
use std::path::{Path, PathBuf};
use std::result;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        Io(err: std::io::Error) { from () }
        CapstoneError(err: capstone::error::Err) { from ()}
        UnsupportedBinary { from() }
        SectionlessBinary { description("Cannot analyze and disassemble a sectionless (section stripped) binary") from() }
        StrippedBinary { description("Cannot analyze and disassemble a stripped binary") from() }
    }
}

type Result<T> = result::Result<T, Error>;

/// The command line state we're interested in
pub struct Config {
    pub exports: bool,
    pub demangle: bool,
    pub crate_name: String,
    pub disassemble: bool,
}

/// A fibulurous Trans-Ductinator which hammifies the Symglob
/// - It knows how to return `Symbol`s
/// When a new binary backend becomes available, `impl` a new TransDuctinator!
pub trait TransDuctinator {
    fn get_arch(&self) -> Option<capstone::Arch>;
    fn symbols(&self, config: &Config) -> Vec<Symbol>;
    fn print_symbols(&self, config: &Config) {
        for symbol in self.symbols(config) {
            symbol.print(config.demangle)
        }
    }
}

// this is all terribly inefficient right now, primarily due to goblin parsing and reading everything
// on earth. that should be fixed soon with some special magiks
impl TransDuctinator for goblin::elf::Elf {
    fn symbols(&self, config: &Config) -> Vec<Symbol> {
        let mut syms = Vec::new();
        let (iter, strtab) = if config.exports {
            (&self.dynsyms, &self.dynstrtab)
        } else {
            (&self.syms, &self.strtab)
        };
        for sym in iter {
            let name = &strtab[sym.st_name() as usize];
            // we skip boring empty symbol names and imports
            if !name.is_empty() && (!config.exports || !sym.is_import()) {
                syms.push(Symbol::new(name, sym.st_value(), sym.st_size() as usize));
            }
        }
        syms.sort_by(|s1, s2| s1.offset.cmp(&s2.offset));
        syms
    }

    fn get_arch(&self) -> Option<capstone::Arch> {
        use goblin::elf::header::*;
        use capstone::Arch::*;
        match self.header.e_machine() {
            EM_AARCH64 => Some(ARM64),
            EM_ARM => Some(ARM),
            EM_X86_64 => Some(X86),
            EM_386 => Some(X86),
            _ => None
        }
    }
}

fn get_crate_name() -> String {
    let mut toml_fd = File::open("Cargo.toml")
                          .expect("No Cargo.toml file found in root directory...");
    let mut toml = String::new();
    let _ = toml_fd.read_to_string(&mut toml);
    let value: toml::Value = toml.parse().expect("Malformed Cargo.toml file");
    let package_name = value.lookup("package.name")
                            .expect("No package.name in Cargo.toml")
                            .as_str()
                            .unwrap()
                            .to_owned()
        // we only replace this if it's a library..., so this breaks on ourself :/
                            .replace("-", "_");
    package_name
}

fn get_target(crate_name: &str) -> Option<PathBuf> {
    let targets = [Path::new("target").join("debug").join(&crate_name),
                   Path::new("target").join("debug").join(&format!("lib{}.so", &crate_name)),
                   Path::new("target").join("debug").join(&format!("lib{}.rlib", &crate_name)),
                   Path::new("target").join("debug").join(&format!("lib{}.a", &crate_name))];
    for target in &targets {
        match File::open(&target) {
            Ok(_) => return Some(target.clone()),
            _ => (),
        }
    }
    None
}

fn valid_disassembly_target(name: &str) -> bool {
    match name {
        ".init" | ".plt" | ".plt.got" | ".text" | ".fini" => true,
        _ => false,
    }
}

#[inline]
fn print_disass(bytes: &[u8], capstone: &capstone::Capstone, symbol: Symbol, demangle: bool) -> Result<()> {
    let offset = symbol.offset as usize;
    let bytes = &bytes[offset .. offset + symbol.size as usize];
    let instructions = capstone.disasm(bytes, symbol.offset)?;
    if !instructions.is_empty() {
        println!("{}:\n{}", symbol.maybe_demangle(demangle), instructions);
    }
    Ok(())
}

fn bias (sym: &goblin::elf::Sym, section: &goblin::elf::SectionHeader) -> u64 {
    (sym.st_value() - section.sh_addr()) + section.sh_offset()
}

fn disassemble_elf (bytes: &mut Cursor<&Vec<u8>>, elf: &goblin::elf::Elf, config: &Config) -> Result<()> {
    let arch = elf.get_arch().ok_or(Error::UnsupportedBinary)?;
    let mut capstone = capstone::Capstone::new(arch)?;
    capstone.att();
    let mode = if elf.little_endian { capstone::Mode::LittleEndian } else { capstone::Mode::BigEndian };
    capstone.mode(&[mode])?;
    let strtab = &elf.strtab;
    let shdr_strtab = &elf.shdr_strtab;
    let bytes = bytes.get_ref();
    let section_headers = &elf.section_headers;
    if section_headers.len() == 0 { return Err(Error::SectionlessBinary)}
    let sections: Vec<(_, &str)> = section_headers.into_iter().map (| section | {
        let section_name = &shdr_strtab[section.sh_name()];
        (section, section_name)
    }).collect();
    let syms = &elf.syms;
    if syms.len() == 0 { return Err(Error::StrippedBinary)}
    // filter the symbols to remove imports and empty symbol names
    let mut elf_syms = syms.into_iter().filter(|sym| !sym.is_import() && !&strtab[sym.st_name()].is_empty()).collect::<Vec<_>>();
    elf_syms.sort_by(|s1, s2| {
        use std::cmp::Ordering::*;
        match s1.st_shndx().cmp(&s2.st_shndx()) {
            Equal => {
                s1.st_value().cmp(&s2.st_value())
            },
            order => order
        }
    });
    let mut current_section = 0;
    let nsyms = elf_syms.len();
    let mut i = 0;
    for sym in &elf_syms {
        //println!("name: {} st_shndx: {}", &strtab[sym.st_name()],  sym.st_shndx());
        //println!("{} {:?}", &strtab[sym.st_name()], sym);
        let is_last = i >= nsyms - 1;
        let section_index = sym.st_shndx();
        if section_index >= sections.len() { continue }
        let (ref section, ref section_name) = sections[sym.st_shndx() as usize];
        if section.sh_type() != goblin::elf::section_header::SHT_PROGBITS || !valid_disassembly_target(section_name) { continue }
        //if name.is_empty() { name = format!("{}@{}", &dynstrtab[sym.st_name()], section_name)}
        if current_section != section_index {
            current_section = section_index;
            println!("Disassembly of section {}\n", section_name);
            match section_name {
                // maybe do some specific plt disassembly stuff
                // this is a hack for printing PLT entries (plt.got is untested and doesn't work)
                &".plt" | &".plt.got" => {
                    let mut start = section.sh_offset() as usize;
                    let size = section.sh_entsize() as usize;
                    let strtab = &elf.dynstrtab;
                    let symbol = Symbol::new(&"PLT", start as u64, size);
                    print_disass(&bytes, &capstone, symbol, config.demangle)?;
                    start += size;
                    for rela in &elf.pltrela {
                        let symindex = rela.r_sym();
                        let sym = elf.dynsyms.get(symindex);
                        let name = &strtab[sym.st_name()];
                        //println!("name: {} offset {:x} size: {} shname: {} shoffset: {:x} shaddr: {:x}", name, rela.r_offset(), size, section_name, section.sh_offset(), section.sh_addr());
                        let symbol = Symbol::new(&name, start as u64, size);
                        print_disass(&bytes, &capstone, symbol, config.demangle)?;
                        start += size;
                    }
                },
                _ => ()
            }
        }
        // we're not doing plt stuff, so regular disassembly
        let name = &strtab[sym.st_name()];
        if name.is_empty() { continue }
        let mut size = sym.st_size() as usize;
        let offset = bias(&sym, &section);
        // we compute the size of unsized symbols on the fly. it sucks. because elf sucks.
        if size == 0 && !is_last {
            let next_sym = &elf_syms[i + 1];
            //println!("i: {} current {}, name{} next: {:?}", i, current_section, name, next_sym);
            if current_section == next_sym.st_shndx() {
                let next_offset = bias(&next_sym, &section);
                size = (next_offset - offset) as usize;
            } else {
                size = section.sh_size() as usize;
            }
        }
        //println!("offset {:x} size: {} section: {}, sh_type: {}", offset, size, section_name, section_header::sht_to_str(section.sh_type()));
        let symbol = Symbol::new(name, offset, size);
        print_disass(&bytes, &capstone, symbol, config.demangle)?;
        i += 1;
    }
    Ok(())
}

fn do_elf (bytes: &mut Cursor<&Vec<u8>>, config: &Config) -> Result<()> {
    let elf = goblin::elf::Elf::parse(bytes)?;
    if config.disassemble {
        disassemble_elf(bytes, &elf, config)?;
    } else {
        elf.print_symbols(config);
    }
    Ok(())
}

fn run(fd: &mut File, config: &Config) -> Result<()> {
    // todo write a generic peek function in goblin you jerk
    let mut magic = [0u8; 16];
    let err = ErrorKind::InvalidInput;
    let metadata = fd.metadata()?;
    let mut buffer = Vec::new();
    fd.read_to_end(&mut buffer)?;
    let mut bytes = &mut Cursor::new(&buffer);
    bytes.read(&mut magic)?;
    bytes.seek(SeekFrom::Start(0))?;
    if &magic[0..goblin::archive::SIZEOF_MAGIC] == goblin::archive::MAGIC {
        let archive = goblin::archive::Archive::parse(bytes, metadata.len() as usize)?;
        let bytes = archive.extract(&format!("{}.0.o", &config.crate_name), bytes)?;
        let mut bytes = Cursor::new(&bytes);
        do_elf(&mut bytes, config)
    } else if &magic[0..4] == goblin::elf::header::ELFMAG {
        do_elf(bytes, config)
    } else {
        Err(Error::from(io::Error::new(err,
                           format!("No binary backend available for target: {:?}", &fd))))
    }
}

fn main() {

    let matches = App::new("cargo-sym")
                      .version("0.0.2")
                      .bin_name("cargo")
                      .settings(&[AppSettings::GlobalVersion, AppSettings::SubcommandRequired])
                      .subcommand(SubCommand::with_name("sym")
                                      .author("m4b <m4b.github.io@gmail.com>")
                                      .about("Prints the debugging symbols in your binary; more \
                                              fancy stuff to come later")
                                      .args(&[Arg::with_name("binary")
                                                  .short("-f")
                                                  .long("file")
                                                  .value_name("BINARY")
                                                  .help("The binary file to read ")
                                                  .takes_value(true),
                                          Arg::with_name("disassemble")
                                              .short("-d")
                                              .long("disassemble")
                                              .value_name("DISASSEMBLE")
                                              .takes_value(false)
                                              .help("Whether to disassaemble or not "),
                                          Arg::with_name("demangle")
                                                  .short("-C")
                                                  .long("demangle")
                                                  .value_name("DEMANGLE")
                                                  .takes_value(false)
                                                  .help("Whether to demangle or not "),
                                              Arg::with_name("exports")
                                                  .short("-e")
                                                  .long("exports")
                                                  .value_name("EXPORTS")
                                                  .takes_value(false)
                                                  .help("Print the exported symbols that are \
                                                         importable by other binaries")]))
                      .get_matches();

    let crate_name = get_crate_name();
    let matches = matches.subcommand_matches("sym").unwrap();
    let demangle = matches.is_present("demangle");
    let exports = matches.is_present("exports");
    let disassemble = matches.is_present("disassemble");
    let config = Config {
        demangle: demangle,
        exports: exports,
        crate_name: crate_name.to_string(),
        disassemble: disassemble,
    };
    let mut fd = match matches.value_of("binary") {
                     Some(binary) => File::open(Path::new(binary)),
                     _ => File::open(get_target(&crate_name).expect("No valid binary found")),
                 }
                 .expect("Cannot open file");

    run(&mut fd, &config)
        .expect(&format!("Cannot read symbols from: {:?}", &fd));

}
<commit_msg>remove -f flag and just use index(1)<commit_after>extern crate clap;
extern crate goblin;
extern crate rustc_demangle;
extern crate toml;
extern crate capstone;
#[macro_use] extern crate quick_error;

pub mod symbol;

use clap::{Arg, App, SubCommand, AppSettings};
use symbol::Symbol;

use std::fs::File;
use std::io::{self, Cursor, Read, Seek, ErrorKind};
use std::io::SeekFrom;
use std::path::{Path, PathBuf};
use std::result;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        Io(err: std::io::Error) { from () }
        CapstoneError(err: capstone::error::Err) { from ()}
        UnsupportedBinary { from() }
        SectionlessBinary { description("Cannot analyze and disassemble a sectionless (section stripped) binary") from() }
        StrippedBinary { description("Cannot analyze and disassemble a stripped binary") from() }
    }
}

type Result<T> = result::Result<T, Error>;

/// The command line state we're interested in
pub struct Config {
    pub exports: bool,
    pub demangle: bool,
    pub crate_name: String,
    pub disassemble: bool,
}

/// A fibulurous Trans-Ductinator which hammifies the Symglob
/// - It knows how to return `Symbol`s
/// When a new binary backend becomes available, `impl` a new TransDuctinator!
pub trait TransDuctinator {
    fn get_arch(&self) -> Option<capstone::Arch>;
    fn symbols(&self, config: &Config) -> Vec<Symbol>;
    fn print_symbols(&self, config: &Config) {
        for symbol in self.symbols(config) {
            symbol.print(config.demangle)
        }
    }
}

// this is all terribly inefficient right now, primarily due to goblin parsing and reading everything
// on earth. that should be fixed soon with some special magiks
impl TransDuctinator for goblin::elf::Elf {
    fn symbols(&self, config: &Config) -> Vec<Symbol> {
        let mut syms = Vec::new();
        let (iter, strtab) = if config.exports {
            (&self.dynsyms, &self.dynstrtab)
        } else {
            (&self.syms, &self.strtab)
        };
        for sym in iter {
            let name = &strtab[sym.st_name() as usize];
            // we skip boring empty symbol names and imports
            if !name.is_empty() && (!config.exports || !sym.is_import()) {
                syms.push(Symbol::new(name, sym.st_value(), sym.st_size() as usize));
            }
        }
        syms.sort_by(|s1, s2| s1.offset.cmp(&s2.offset));
        syms
    }

    fn get_arch(&self) -> Option<capstone::Arch> {
        use goblin::elf::header::*;
        use capstone::Arch::*;
        match self.header.e_machine() {
            EM_AARCH64 => Some(ARM64),
            EM_ARM => Some(ARM),
            EM_X86_64 => Some(X86),
            EM_386 => Some(X86),
            _ => None
        }
    }
}

fn get_crate_name() -> String {
    let mut toml_fd = File::open("Cargo.toml")
                          .expect("No Cargo.toml file found in root directory...");
    let mut toml = String::new();
    let _ = toml_fd.read_to_string(&mut toml);
    let value: toml::Value = toml.parse().expect("Malformed Cargo.toml file");
    let package_name = value.lookup("package.name")
                            .expect("No package.name in Cargo.toml")
                            .as_str()
                            .unwrap()
                            .to_owned()
        // we only replace this if it's a library..., so this breaks on ourself :/
                            .replace("-", "_");
    package_name
}

fn get_target(crate_name: &str) -> Option<PathBuf> {
    let targets = [Path::new("target").join("debug").join(&crate_name),
                   Path::new("target").join("debug").join(&format!("lib{}.so", &crate_name)),
                   Path::new("target").join("debug").join(&format!("lib{}.rlib", &crate_name)),
                   Path::new("target").join("debug").join(&format!("lib{}.a", &crate_name))];
    for target in &targets {
        match File::open(&target) {
            Ok(_) => return Some(target.clone()),
            _ => (),
        }
    }
    None
}

fn valid_disassembly_target(name: &str) -> bool {
    match name {
        ".init" | ".plt" | ".plt.got" | ".text" | ".fini" => true,
        _ => false,
    }
}

#[inline]
fn print_disass(bytes: &[u8], capstone: &capstone::Capstone, symbol: Symbol, demangle: bool) -> Result<()> {
    let offset = symbol.offset as usize;
    let bytes = &bytes[offset .. offset + symbol.size as usize];
    let instructions = capstone.disasm(bytes, symbol.offset)?;
    if !instructions.is_empty() {
        println!("{}:\n{}", symbol.maybe_demangle(demangle), instructions);
    }
    Ok(())
}

fn bias (sym: &goblin::elf::Sym, section: &goblin::elf::SectionHeader) -> u64 {
    (sym.st_value() - section.sh_addr()) + section.sh_offset()
}

fn disassemble_elf (bytes: &mut Cursor<&Vec<u8>>, elf: &goblin::elf::Elf, config: &Config) -> Result<()> {
    let arch = elf.get_arch().ok_or(Error::UnsupportedBinary)?;
    let mut capstone = capstone::Capstone::new(arch)?;
    capstone.att();
    let mode = if elf.little_endian { capstone::Mode::LittleEndian } else { capstone::Mode::BigEndian };
    capstone.mode(&[mode])?;
    let strtab = &elf.strtab;
    let shdr_strtab = &elf.shdr_strtab;
    let bytes = bytes.get_ref();
    let section_headers = &elf.section_headers;
    if section_headers.len() == 0 { return Err(Error::SectionlessBinary)}
    let sections: Vec<(_, &str)> = section_headers.into_iter().map (| section | {
        let section_name = &shdr_strtab[section.sh_name()];
        (section, section_name)
    }).collect();
    let syms = &elf.syms;
    if syms.len() == 0 { return Err(Error::StrippedBinary)}
    // filter the symbols to remove imports and empty symbol names
    let mut elf_syms = syms.into_iter().filter(|sym| !sym.is_import() && !&strtab[sym.st_name()].is_empty()).collect::<Vec<_>>();
    elf_syms.sort_by(|s1, s2| {
        use std::cmp::Ordering::*;
        match s1.st_shndx().cmp(&s2.st_shndx()) {
            Equal => {
                s1.st_value().cmp(&s2.st_value())
            },
            order => order
        }
    });
    let mut current_section = 0;
    let nsyms = elf_syms.len();
    let mut i = 0;
    for sym in &elf_syms {
        //println!("name: {} st_shndx: {}", &strtab[sym.st_name()],  sym.st_shndx());
        //println!("{} {:?}", &strtab[sym.st_name()], sym);
        let is_last = i >= nsyms - 1;
        let section_index = sym.st_shndx();
        if section_index >= sections.len() { continue }
        let (ref section, ref section_name) = sections[sym.st_shndx() as usize];
        if section.sh_type() != goblin::elf::section_header::SHT_PROGBITS || !valid_disassembly_target(section_name) { continue }
        //if name.is_empty() { name = format!("{}@{}", &dynstrtab[sym.st_name()], section_name)}
        if current_section != section_index {
            current_section = section_index;
            println!("Disassembly of section {}\n", section_name);
            match section_name {
                // maybe do some specific plt disassembly stuff
                // this is a hack for printing PLT entries (plt.got is untested and doesn't work)
                &".plt" | &".plt.got" => {
                    let mut start = section.sh_offset() as usize;
                    let size = section.sh_entsize() as usize;
                    let strtab = &elf.dynstrtab;
                    let symbol = Symbol::new(&"PLT", start as u64, size);
                    print_disass(&bytes, &capstone, symbol, config.demangle)?;
                    start += size;
                    for rela in &elf.pltrela {
                        let symindex = rela.r_sym();
                        let sym = elf.dynsyms.get(symindex);
                        let name = &strtab[sym.st_name()];
                        //println!("name: {} offset {:x} size: {} shname: {} shoffset: {:x} shaddr: {:x}", name, rela.r_offset(), size, section_name, section.sh_offset(), section.sh_addr());
                        let symbol = Symbol::new(&name, start as u64, size);
                        print_disass(&bytes, &capstone, symbol, config.demangle)?;
                        start += size;
                    }
                },
                _ => ()
            }
        }
        // we're not doing plt stuff, so regular disassembly
        let name = &strtab[sym.st_name()];
        if name.is_empty() { continue }
        let mut size = sym.st_size() as usize;
        let offset = bias(&sym, &section);
        // we compute the size of unsized symbols on the fly. it sucks. because elf sucks.
        if size == 0 && !is_last {
            let next_sym = &elf_syms[i + 1];
            //println!("i: {} current {}, name{} next: {:?}", i, current_section, name, next_sym);
            if current_section == next_sym.st_shndx() {
                let next_offset = bias(&next_sym, &section);
                size = (next_offset - offset) as usize;
            } else {
                size = section.sh_size() as usize;
            }
        }
        //println!("offset {:x} size: {} section: {}, sh_type: {}", offset, size, section_name, section_header::sht_to_str(section.sh_type()));
        let symbol = Symbol::new(name, offset, size);
        print_disass(&bytes, &capstone, symbol, config.demangle)?;
        i += 1;
    }
    Ok(())
}

fn do_elf (bytes: &mut Cursor<&Vec<u8>>, config: &Config) -> Result<()> {
    let elf = goblin::elf::Elf::parse(bytes)?;
    if config.disassemble {
        disassemble_elf(bytes, &elf, config)?;
    } else {
        elf.print_symbols(config);
    }
    Ok(())
}

fn run(fd: &mut File, config: &Config) -> Result<()> {
    // todo write a generic peek function in goblin you jerk
    let mut magic = [0u8; 16];
    let err = ErrorKind::InvalidInput;
    let metadata = fd.metadata()?;
    let mut buffer = Vec::new();
    fd.read_to_end(&mut buffer)?;
    let mut bytes = &mut Cursor::new(&buffer);
    bytes.read(&mut magic)?;
    bytes.seek(SeekFrom::Start(0))?;
    if &magic[0..goblin::archive::SIZEOF_MAGIC] == goblin::archive::MAGIC {
        let archive = goblin::archive::Archive::parse(bytes, metadata.len() as usize)?;
        let bytes = archive.extract(&format!("{}.0.o", &config.crate_name), bytes)?;
        let mut bytes = Cursor::new(&bytes);
        do_elf(&mut bytes, config)
    } else if &magic[0..4] == goblin::elf::header::ELFMAG {
        do_elf(bytes, config)
    } else {
        Err(Error::from(io::Error::new(err,
                           format!("No binary backend available for target: {:?}", &fd))))
    }
}

fn main() {

    let matches = App::new("cargo-sym")
                      .version("0.0.2")
                      .bin_name("cargo")
                      .settings(&[AppSettings::GlobalVersion, AppSettings::SubcommandRequired])
                      .subcommand(SubCommand::with_name("sym")
                                      .author("m4b <m4b.github.io@gmail.com>")
                                      .about("Prints the debugging symbols in your binary; more \
                                              fancy stuff to come later")
                                      .args(&[Arg::with_name("binary")
                                                  .help("The binary file to read ")
                                                  .required(false)
                                                  .index(1),
                                          Arg::with_name("disassemble")
                                              .short("-d")
                                              .long("disassemble")
                                              .value_name("DISASSEMBLE")
                                              .takes_value(false)
                                              .help("Whether to disassaemble or not "),
                                          Arg::with_name("demangle")
                                                  .short("-C")
                                                  .long("demangle")
                                                  .value_name("DEMANGLE")
                                                  .takes_value(false)
                                                  .help("Whether to demangle or not "),
                                              Arg::with_name("exports")
                                                  .short("-e")
                                                  .long("exports")
                                                  .value_name("EXPORTS")
                                                  .takes_value(false)
                                                  .help("Print the exported symbols that are \
                                                         importable by other binaries")]))
                      .get_matches();

    let crate_name = get_crate_name();
    let matches = matches.subcommand_matches("sym").unwrap();
    let demangle = matches.is_present("demangle");
    let exports = matches.is_present("exports");
    let disassemble = matches.is_present("disassemble");
    let config = Config {
        demangle: demangle,
        exports: exports,
        crate_name: crate_name.to_string(),
        disassemble: disassemble,
    };
    let mut fd = match matches.value_of("binary") {
                     Some(binary) => File::open(Path::new(binary)),
                     _ => File::open(get_target(&crate_name).expect("No valid binary found")),
                 }
                 .expect("Cannot open file");

    run(&mut fd, &config)
        .expect(&format!("Cannot read symbols from: {:?}", &fd));
}
<|endoftext|>


<commit_before>extern crate ar;
extern crate clap;
extern crate libflate;
extern crate tar;
extern crate tempfile;
extern crate time as crates_time;
extern crate xz2;
extern crate zip;

use std::fs;
use std::io;
use std::time;

use std::rc::Rc;
use std::vec::Vec;

use clap::{Arg, App};

use libflate::gzip;
use tempfile::tempfile;

// magic:
use std::io::Seek;
use std::io::Write;

#[derive(Debug)]
struct Node<T> {
    next: Option<Rc<Node<T>>>,
    value: T,
}

impl<T: Clone> Node<T> {
    fn head(obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: None,
            value: obj,
        })
    }

    fn plus(what: &Rc<Node<T>>, obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: Some(what.clone()),
            value: obj
        })
    }

    fn to_vec(&self) -> Vec<T> {
        let mut ret = Vec::new();
        let mut val = self;
        loop {
            ret.push(val.value.clone());
            if let Some(ref next) = val.next {
                val = next;
            } else {
                break;
            }
        }
        ret.reverse();
        ret
    }
}

struct OutputTo {
    path: Rc<Node<String>>,
    size: u64,
    atime: u64,
    mtime: u64,
    ctime: u64,
    btime: u64,
}

impl OutputTo {
    fn warn(&self, msg: String) {
        writeln!(io::stderr(), "TODO: {}", msg).expect("stderr");
    }

    fn raw<F: io::Read + ?Sized>(&self, mut file: &mut F) -> io::Result<()> {
        let stdout = io::stdout();
        let mut stdout = stdout.lock();
        writeln!(stdout, "{:?} {} {} {} {} {}",
            self.path.to_vec(),
            self.size,
            self.atime, self.mtime, self.ctime, self.btime
        )?;

        io::copy(&mut file, &mut stdout).and_then(move |written|
            if written != self.size {
                Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    format!("expecting to write {} but wrote {}", self.size, written)))
            } else {
                Ok(())
            })
    }

    fn from_file(path: &str, fd: &fs::File) -> io::Result<OutputTo> {
        let meta = fd.metadata()?;
        Ok(OutputTo {
            path: Node::head(path.to_string()),
            size: meta.len(),
            atime: meta.accessed().map(simple_time_sys)?,
            mtime: meta.modified().map(simple_time_sys)?,
            ctime: simple_time_ctime(&meta),
            btime: simple_time_btime(&meta)?,
        })
    }

    fn with_path(&self, path: &str) -> OutputTo {
        OutputTo {
            path: Node::plus(&self.path, path.to_string()),
            size: 0,
            atime: 0,
            mtime: 0,
            ctime: 0,
            btime: 0,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum FileType {
    GZip,
    Zip,
    Tar,
    Ar,
    BZip2,
    Xz,
    Other,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Status {
    Done,
    Rewind,
}

fn read_octal(bytes: &[u8]) -> Option<u32> {
    let mut start = 0;
    while start < bytes.len() && b' ' == bytes[start] {
        start += 1;
    }

    let mut end = bytes.len() - 1;
    while end > start && (b' ' == bytes[end] || 0 == bytes[end]) {
        end -= 1;
    }

    if let Ok(string) = std::str::from_utf8(&bytes[start..(end+1)]) {
        if let Ok(val) = u32::from_str_radix(string, 8) {
            return Some(val);
        }
    }
    None
}

fn simple_time(dur: time::Duration) -> u64 {
    dur.as_secs().checked_mul(1_000_000_000)
        .map_or(0, |nanos| nanos + dur.subsec_nanos() as u64)
}

fn simple_time_sys(val: time::SystemTime) -> u64 {
    val.duration_since(time::UNIX_EPOCH).map(simple_time).unwrap_or(0)
}

fn simple_time_tm(val: crates_time::Tm) -> u64 {
    let timespec = val.to_timespec();
    simple_time(time::Duration::new(timespec.sec as u64, timespec.nsec as u32))
}

fn simple_time_btime(val: &fs::Metadata) -> io::Result<u64> {
    match val.created() {
        Ok(time) => Ok(simple_time_sys(time)),
        // "Other" is how "unsupported" is represented here; ew.
        Err(ref e) if e.kind() == io::ErrorKind::Other => Ok(0),
        Err(other) => Err(other),
    }
}

// TODO: I really feel this should be exposed by Rust, without cfg.
fn simple_time_ctime(val: &fs::Metadata) -> u64 {
    #[cfg(linux)] {
        use std::os::linux::fs::MetadataExt;
        let ctime: i64 = val.st_ctime();
        if ctime <= 0 {
            0
        } else {
            ctime as u64
        }
    }

    #[cfg(not(linux))] {
        0
    }
}

fn is_probably_tar(header: &[u8]) -> bool {
    if header.len() < 512 {
        return false;
    }

    if let Some(checksum) = read_octal(&header[148..156]) {
        let mut sum: u32 = (b' ' as u32) * 8;
        for i in 0..148 {
            sum += header[i] as u32;
        }
        for i in 156..512 {
            sum += header[i] as u32;
        }

        if checksum == sum {
            return true;
        }
    }

    return false;
}

fn identify<'a, T: io::BufRead + ?Sized>(fd: &mut T) -> io::Result<FileType> {
    let header = fd.fill_buf()?;
    if header.len() >= 20
        && 0x1f == header[0] && 0x8b == header[1] {
        Ok(FileType::GZip)
   } else if header.len() >= 152
        && b'P' == header[0] && b'K' == header[1]
        && 0x03 == header[2] && 0x04 == header[3] {
        Ok(FileType::Zip)
    } else if header.len() > 257 + 10
        && b'u' == header[257] && b's' == header[258]
        && b't' == header[259] && b'a' == header[260]
        && b'r' == header[261]
        && (
            (0 == header[262] && b'0' == header[263] && b'0' == header[264]) ||
            (b' ' == header[262] && b' ' == header[263] && 0 == header[264])
        ) {
        Ok(FileType::Tar)
    } else if header.len() > 8
        && b'!' == header[0] && b'<' == header[1]
        && b'a' == header[2] && b'r' == header[3]
        && b'c' == header[4] && b'h' == header[5]
        && b'>' == header[6] && b'\n' == header[7] {
        Ok(FileType::Ar)
    } else if header.len() > 40
        && b'B' == header[0] && b'Z' == header[1]
        && b'h' == header[2] && 0x31 == header[3]
        && 0x41 == header[4] && 0x59 == header[5]
        && 0x26 == header[6] {
        Ok(FileType::BZip2)
    } else if header.len() > 6
        && 0xfd == header[0] && b'7' == header[1]
        && b'z' == header[2] && b'X' == header[3]
        && b'Z' == header[4] && 0 == header[5] {
        Ok(FileType::Xz)
    } else if is_probably_tar(header) {
        Ok(FileType::Tar)
    } else {
        Ok(FileType::Other)
    }
}

trait Tee {
    fn normal(&mut self) -> &mut io::BufRead;
    fn abort(&mut self) -> io::Result<&mut io::BufRead>;
}

struct TempFileTee<'a> {
    fp: Box<io::BufRead + 'a>,
    tmp: fs::File,
}

impl<'a> TempFileTee<'a> {
    fn new<U: 'a + io::Read>(from: U) -> io::Result<TempFileTee<'a>> {
        Ok(TempFileTee {
            fp: Box::new(io::BufReader::new(from)),
            tmp: tempfile()?
        })
    }
}

impl<'a> Tee for TempFileTee<'a> {
    fn normal(&mut self) -> &mut io::BufRead {
        &mut self.fp
    }
    fn abort(&mut self) -> io::Result<&mut io::BufRead> {
        unimplemented!();
    }
}

struct BufReaderTee<T> {
    fp: Box<T>,
}

impl<T> BufReaderTee<T> {
    // TODO: return type?
    fn new<U: io::Read>(from: U) -> BufReaderTee<io::BufReader<U>> {
        BufReaderTee {
            fp: Box::new(io::BufReader::new(from))
        }
    }
}

impl<T> Tee for BufReaderTee<T>
    where T: io::BufRead + io::Seek
{
    fn normal(&mut self) -> &mut io::BufRead {
        &mut self.fp
    }

    fn abort(&mut self) -> io::Result<&mut io::BufRead> {
        self.fp.seek(io::SeekFrom::Start(0))?;
        Ok(self.normal())
    }
}

fn count_bytes<'a, R: io::Read>(mut fd: &mut R) -> io::Result<u64> {
    let mut block = [0u8; 4096];
    let mut count = 0u64;
    loop {
        let found = fd.read(&mut block[..])?;
        if 0 == found {
            break;
        }
        count += found as u64;
    }
    Ok(count)
}

fn unpack_or_raw<'a, 'b, F, T: 'b + io::Read>(mut fd: Box<Tee + 'a>, output: &OutputTo, f: F) -> io::Result<Status>
where F: FnOnce(&'b mut io::BufRead) -> io::Result<T>
{
    let process_decoded = match f(fd.normal()) {
        Ok(dec) => Ok(unpack(Box::new(TempFileTee::new(dec)?), output)?),
        Err(e) => Err(e),
    };

    match process_decoded {
        Ok(status) => Ok(status),
        Err(e) => {
            output.warn(format!(
                "thought we could unpack '{:?}' but we couldn't: {}",
                output.path, e));
            output.raw(fd.abort()?)?;
            Ok(Status::Done)
        }
    }
}

fn unpack<'a>(mut fd: Box<Tee + 'a>, output: &OutputTo) -> io::Result<Status> {
    match identify(fd.normal())? {
        FileType::GZip => {
            unpack_or_raw(fd, output, move |han| gzip::Decoder::new(han))
        },
//        FileType::Xz => {
//            unpack_or_raw(fd, output,
//                |fd| Ok(Box::new(TempFileTee::new(xz2::bufread::XzDecoder::new(fd)))))
//        },
        FileType::Ar if output.path.value.ends_with(".deb") => {
            let mut decoder = ar::Archive::new(fd.normal());
            while let Some(entry) = decoder.next_entry() {
                let entry = entry?;
                let new_output = output.with_path(entry.header().identifier());
                unpack(Box::new(TempFileTee::new(entry)?), &new_output)?;
            }
            Ok(Status::Done)
        },
        FileType::Tar => {
            let mut decoder = tar::Archive::new(fd.normal());
            for entry in decoder.entries()? {
                let entry = entry?;
                let new_output = output.with_path(entry.path()?.to_str().expect("valid utf-8"));
                unpack(Box::new(TempFileTee::new(entry)?), &new_output)?;
            }
            Ok(Status::Done)
        },
        FileType::Zip => {
            let mut temp = tempfile()?;
            io::copy(fd.normal(), &mut temp)?;
            let mut zip = zip::ZipArchive::new(temp)?;

            for i in 0..zip.len() {

                // well, this has gone rather poorly
                let mut new_output = output.with_path(zip.by_index(i)?.name());

                let file_result = {
                    let entry = zip.by_index(i)?;

                    new_output.size = entry.size();
                    new_output.mtime = simple_time_tm(entry.last_modified());
                    let reader = Box::new(TempFileTee::new(entry)?);
                    unpack(reader, &new_output)?
                };
                match file_result {
                    Status::Done => (),
                    Status::Rewind => {
                        // TODO: update output? ? handling? ???
                        new_output.raw(&mut zip.by_index(i)?)?
                    },
                };
            }

            Ok(Status::Done)
        },

        // TODO: unimplemented!()
        _ => {
            Ok(Status::Rewind)
        },
    }
}

fn output_raw(file: &mut fs::File, output: &OutputTo) -> io::Result<()> {
    file.seek(io::SeekFrom::Start(0))?;
    output.raw(file)?;
    Ok(())
}

fn process_real_path(path: &str) -> io::Result<()> {
    let mut file = fs::File::open(path).unwrap();
    let output = OutputTo::from_file(
        path,
        &file
    )?;

    let attempt = {
//        let read = BufReaderTee::new::<BufReaderTee<io::BufReader<fs::File>>>(file);
        let read = TempFileTee::new(&file)?;
        unpack(Box::new(read), &output)
    };

    match attempt {
        Ok(Status::Done) => Ok(()),
        Ok(Status::Rewind) => output_raw(&mut file, &output),
        Err(e) => Err(e)
    }
}

fn real_main() -> u8 {

    let matches = App::new("contentin")
                    .arg(Arg::with_name("to-tar")
                         .long("to-tar")
                         .help("emit a tar file")
                         .required(true))
                    .arg(Arg::with_name("INPUT")
                         .required(true)
                         .multiple(true))
                    .get_matches();

    for path in matches.values_of("INPUT").unwrap() {
        if let Err(e) = process_real_path(path) {
            if let Err(_) = writeln!(io::stderr(), "fatal: processing '{}': {}", path, e) {
                return 6;
            }

            return 4;
        }
    }
    return 0;
}

fn main() {
    std::process::exit(real_main() as i32)
}
<commit_msg>Revert "can't work out the lifetimes for that inline"<commit_after>extern crate ar;
extern crate clap;
extern crate libflate;
extern crate tar;
extern crate tempfile;
extern crate time as crates_time;
extern crate xz2;
extern crate zip;

use std::fs;
use std::io;
use std::time;

use std::rc::Rc;
use std::vec::Vec;

use clap::{Arg, App};

use libflate::gzip;
use tempfile::tempfile;

// magic:
use std::io::Seek;
use std::io::Write;

#[derive(Debug)]
struct Node<T> {
    next: Option<Rc<Node<T>>>,
    value: T,
}

impl<T: Clone> Node<T> {
    fn head(obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: None,
            value: obj,
        })
    }

    fn plus(what: &Rc<Node<T>>, obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: Some(what.clone()),
            value: obj
        })
    }

    fn to_vec(&self) -> Vec<T> {
        let mut ret = Vec::new();
        let mut val = self;
        loop {
            ret.push(val.value.clone());
            if let Some(ref next) = val.next {
                val = next;
            } else {
                break;
            }
        }
        ret.reverse();
        ret
    }
}

struct OutputTo {
    path: Rc<Node<String>>,
    size: u64,
    atime: u64,
    mtime: u64,
    ctime: u64,
    btime: u64,
}

impl OutputTo {
    fn warn(&self, msg: String) {
        writeln!(io::stderr(), "TODO: {}", msg).expect("stderr");
    }

    fn raw<F: io::Read + ?Sized>(&self, mut file: &mut F) -> io::Result<()> {
        let stdout = io::stdout();
        let mut stdout = stdout.lock();
        writeln!(stdout, "{:?} {} {} {} {} {}",
            self.path.to_vec(),
            self.size,
            self.atime, self.mtime, self.ctime, self.btime
        )?;

        io::copy(&mut file, &mut stdout).and_then(move |written|
            if written != self.size {
                Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    format!("expecting to write {} but wrote {}", self.size, written)))
            } else {
                Ok(())
            })
    }

    fn from_file(path: &str, fd: &fs::File) -> io::Result<OutputTo> {
        let meta = fd.metadata()?;
        Ok(OutputTo {
            path: Node::head(path.to_string()),
            size: meta.len(),
            atime: meta.accessed().map(simple_time_sys)?,
            mtime: meta.modified().map(simple_time_sys)?,
            ctime: simple_time_ctime(&meta),
            btime: simple_time_btime(&meta)?,
        })
    }

    fn with_path(&self, path: &str) -> OutputTo {
        OutputTo {
            path: Node::plus(&self.path, path.to_string()),
            size: 0,
            atime: 0,
            mtime: 0,
            ctime: 0,
            btime: 0,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum FileType {
    GZip,
    Zip,
    Tar,
    Ar,
    BZip2,
    Xz,
    Other,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Status {
    Done,
    Rewind,
}

fn read_octal(bytes: &[u8]) -> Option<u32> {
    let mut start = 0;
    while start < bytes.len() && b' ' == bytes[start] {
        start += 1;
    }

    let mut end = bytes.len() - 1;
    while end > start && (b' ' == bytes[end] || 0 == bytes[end]) {
        end -= 1;
    }

    if let Ok(string) = std::str::from_utf8(&bytes[start..(end+1)]) {
        if let Ok(val) = u32::from_str_radix(string, 8) {
            return Some(val);
        }
    }
    None
}

fn simple_time(dur: time::Duration) -> u64 {
    dur.as_secs().checked_mul(1_000_000_000)
        .map_or(0, |nanos| nanos + dur.subsec_nanos() as u64)
}

fn simple_time_sys(val: time::SystemTime) -> u64 {
    val.duration_since(time::UNIX_EPOCH).map(simple_time).unwrap_or(0)
}

fn simple_time_tm(val: crates_time::Tm) -> u64 {
    let timespec = val.to_timespec();
    simple_time(time::Duration::new(timespec.sec as u64, timespec.nsec as u32))
}

fn simple_time_btime(val: &fs::Metadata) -> io::Result<u64> {
    match val.created() {
        Ok(time) => Ok(simple_time_sys(time)),
        // "Other" is how "unsupported" is represented here; ew.
        Err(ref e) if e.kind() == io::ErrorKind::Other => Ok(0),
        Err(other) => Err(other),
    }
}

// TODO: I really feel this should be exposed by Rust, without cfg.
fn simple_time_ctime(val: &fs::Metadata) -> u64 {
    #[cfg(linux)] {
        use std::os::linux::fs::MetadataExt;
        let ctime: i64 = val.st_ctime();
        if ctime <= 0 {
            0
        } else {
            ctime as u64
        }
    }

    #[cfg(not(linux))] {
        0
    }
}

fn is_probably_tar(header: &[u8]) -> bool {
    if header.len() < 512 {
        return false;
    }

    if let Some(checksum) = read_octal(&header[148..156]) {
        let mut sum: u32 = (b' ' as u32) * 8;
        for i in 0..148 {
            sum += header[i] as u32;
        }
        for i in 156..512 {
            sum += header[i] as u32;
        }

        if checksum == sum {
            return true;
        }
    }

    return false;
}

fn identify<'a, T: io::BufRead + ?Sized>(fd: &mut T) -> io::Result<FileType> {
    let header = fd.fill_buf()?;
    if header.len() >= 20
        && 0x1f == header[0] && 0x8b == header[1] {
        Ok(FileType::GZip)
   } else if header.len() >= 152
        && b'P' == header[0] && b'K' == header[1]
        && 0x03 == header[2] && 0x04 == header[3] {
        Ok(FileType::Zip)
    } else if header.len() > 257 + 10
        && b'u' == header[257] && b's' == header[258]
        && b't' == header[259] && b'a' == header[260]
        && b'r' == header[261]
        && (
            (0 == header[262] && b'0' == header[263] && b'0' == header[264]) ||
            (b' ' == header[262] && b' ' == header[263] && 0 == header[264])
        ) {
        Ok(FileType::Tar)
    } else if header.len() > 8
        && b'!' == header[0] && b'<' == header[1]
        && b'a' == header[2] && b'r' == header[3]
        && b'c' == header[4] && b'h' == header[5]
        && b'>' == header[6] && b'\n' == header[7] {
        Ok(FileType::Ar)
    } else if header.len() > 40
        && b'B' == header[0] && b'Z' == header[1]
        && b'h' == header[2] && 0x31 == header[3]
        && 0x41 == header[4] && 0x59 == header[5]
        && 0x26 == header[6] {
        Ok(FileType::BZip2)
    } else if header.len() > 6
        && 0xfd == header[0] && b'7' == header[1]
        && b'z' == header[2] && b'X' == header[3]
        && b'Z' == header[4] && 0 == header[5] {
        Ok(FileType::Xz)
    } else if is_probably_tar(header) {
        Ok(FileType::Tar)
    } else {
        Ok(FileType::Other)
    }
}

trait Tee {
    fn normal(&mut self) -> &mut io::BufRead;
    fn abort(&mut self) -> io::Result<&mut io::BufRead>;
}

struct TempFileTee<'a> {
    fp: Box<io::BufRead + 'a>,
    tmp: fs::File,
}

impl<'a> TempFileTee<'a> {
    fn new<U: 'a + io::Read>(from: U) -> io::Result<TempFileTee<'a>> {
        Ok(TempFileTee {
            fp: Box::new(io::BufReader::new(from)),
            tmp: tempfile()?
        })
    }
}

impl<'a> Tee for TempFileTee<'a> {
    fn normal(&mut self) -> &mut io::BufRead {
        &mut self.fp
    }
    fn abort(&mut self) -> io::Result<&mut io::BufRead> {
        unimplemented!();
    }
}

struct BufReaderTee<T> {
    fp: Box<T>,
}

impl<T> BufReaderTee<T> {
    // TODO: return type?
    fn new<U: io::Read>(from: U) -> BufReaderTee<io::BufReader<U>> {
        BufReaderTee {
            fp: Box::new(io::BufReader::new(from))
        }
    }
}

impl<T> Tee for BufReaderTee<T>
    where T: io::BufRead + io::Seek
{
    fn normal(&mut self) -> &mut io::BufRead {
        &mut self.fp
    }

    fn abort(&mut self) -> io::Result<&mut io::BufRead> {
        self.fp.seek(io::SeekFrom::Start(0))?;
        Ok(self.normal())
    }
}

fn count_bytes<'a, R: io::Read>(mut fd: &mut R) -> io::Result<u64> {
    let mut block = [0u8; 4096];
    let mut count = 0u64;
    loop {
        let found = fd.read(&mut block[..])?;
        if 0 == found {
            break;
        }
        count += found as u64;
    }
    Ok(count)
}

fn unpack<'a>(mut fd: Box<Tee + 'a>, output: &OutputTo) -> io::Result<Status> {
    match identify(fd.normal())? {
        FileType::GZip => {
            let process_decoded = match gzip::Decoder::new(fd.normal()) {
                Ok(dec) => Ok(unpack(Box::new(TempFileTee::new(dec)?), output)?),
                Err(e) => Err(e),
            };

            match process_decoded {
                Ok(status) => Ok(status),
                Err(e) => {
                    output.warn(format!(
                        "thought we could unpack '{:?}' but we couldn't: {}",
                        output.path, e));
                    output.raw(fd.abort()?)?;
                    Ok(Status::Done)
                }
            }
        },
//        FileType::Xz => {
//            unpack_or_raw(fd, output,
//                |fd| Ok(Box::new(TempFileTee::new(xz2::bufread::XzDecoder::new(fd)))))
//        },
        FileType::Ar if output.path.value.ends_with(".deb") => {
            let mut decoder = ar::Archive::new(fd.normal());
            while let Some(entry) = decoder.next_entry() {
                let entry = entry?;
                let new_output = output.with_path(entry.header().identifier());
                unpack(Box::new(TempFileTee::new(entry)?), &new_output)?;
            }
            Ok(Status::Done)
        },
        FileType::Tar => {
            let mut decoder = tar::Archive::new(fd.normal());
            for entry in decoder.entries()? {
                let entry = entry?;
                let new_output = output.with_path(entry.path()?.to_str().expect("valid utf-8"));
                unpack(Box::new(TempFileTee::new(entry)?), &new_output)?;
            }
            Ok(Status::Done)
        },
        FileType::Zip => {
            let mut temp = tempfile()?;
            io::copy(fd.normal(), &mut temp)?;
            let mut zip = zip::ZipArchive::new(temp)?;

            for i in 0..zip.len() {

                // well, this has gone rather poorly
                let mut new_output = output.with_path(zip.by_index(i)?.name());

                let file_result = {
                    let entry = zip.by_index(i)?;

                    new_output.size = entry.size();
                    new_output.mtime = simple_time_tm(entry.last_modified());
                    let reader = Box::new(TempFileTee::new(entry)?);
                    unpack(reader, &new_output)?
                };
                match file_result {
                    Status::Done => (),
                    Status::Rewind => {
                        // TODO: update output? ? handling? ???
                        new_output.raw(&mut zip.by_index(i)?)?
                    },
                };
            }

            Ok(Status::Done)
        },

        // TODO: unimplemented!()
        _ => {
            Ok(Status::Rewind)
        },
    }
}

fn output_raw(file: &mut fs::File, output: &OutputTo) -> io::Result<()> {
    file.seek(io::SeekFrom::Start(0))?;
    output.raw(file)?;
    Ok(())
}

fn process_real_path(path: &str) -> io::Result<()> {
    let mut file = fs::File::open(path).unwrap();
    let output = OutputTo::from_file(
        path,
        &file
    )?;

    let attempt = {
//        let read = BufReaderTee::new::<BufReaderTee<io::BufReader<fs::File>>>(file);
        let read = TempFileTee::new(&file)?;
        unpack(Box::new(read), &output)
    };

    match attempt {
        Ok(Status::Done) => Ok(()),
        Ok(Status::Rewind) => output_raw(&mut file, &output),
        Err(e) => Err(e)
    }
}

fn real_main() -> u8 {

    let matches = App::new("contentin")
                    .arg(Arg::with_name("to-tar")
                         .long("to-tar")
                         .help("emit a tar file")
                         .required(true))
                    .arg(Arg::with_name("INPUT")
                         .required(true)
                         .multiple(true))
                    .get_matches();

    for path in matches.values_of("INPUT").unwrap() {
        if let Err(e) = process_real_path(path) {
            if let Err(_) = writeln!(io::stderr(), "fatal: processing '{}': {}", path, e) {
                return 6;
            }

            return 4;
        }
    }
    return 0;
}

fn main() {
    std::process::exit(real_main() as i32)
}
<|endoftext|>


<commit_before>extern crate clipboard;
extern crate byteorder;
extern crate argparse;

use clipboard::ClipboardContext;
use std::net::{TcpListener,TcpStream};
use std::io::{Error, ErrorKind, Result, Read,Write};
use std::thread;
use byteorder::{ReadBytesExt, WriteBytesExt,  LittleEndian};
use std::sync::mpsc::{Sender, channel};
use std::time::Duration;
use argparse::{ArgumentParser, Store};

struct Config {
    port : u16,
    localip : String,
    outsideip : String
}

fn run_reading(stream : &mut TcpStream,content : Sender<String>) -> Result<()>{
    loop {
        println!("Waiting for a data");
        let length : u32 = try!(stream.read_u32::<LittleEndian>());
        if length == 0{
            // ignore empty
            continue;
        }
        println!("Got the data size: {}", length);
        let mut data = vec![0u8;length as usize];
        try!(stream.take(length as u64).read_to_end(&mut data));
        match String::from_utf8(data){
            Ok(s) => {
                println!("Received new content from the other side: {}", s);
                let _ = content.send(s);
            }
            Err(_) => {
                println!("Failed to convert received content to string.");
            }
        };
    }
}

fn run_sync(stream : &mut TcpStream) -> Result<()>{
    let mut ctx = match ClipboardContext::new(){
        Ok(c) => c,
        Err(_) => return Err(Error::new(ErrorKind::Other,"Failed to create clipboard context"))
    };
    // println!("{}", ctx.get_contents().unwrap());
    //
    // ctx.set_contents("the new content".to_owned()).unwrap();
    let mut current_content = match ctx.get_contents(){
        Ok(s) => s,
        Err(_) => "".to_owned()
    };
    let (wx, rx) = channel::<String>();
    // // create thread which will read the stream for changes from outside, and apply changes to this system
    //
    let mut reading = stream.try_clone().unwrap();
    thread::spawn(move||{run_reading(&mut reading,wx)});
    let mut writer = stream.try_clone().unwrap();

    println!("initial clipboard value is: {}",current_content );

    loop{
        thread::sleep(Duration::new(1,0));
        println!("Checking for new content!");
        match ctx.get_contents(){
            Ok(s) => {

                if s != current_content{
                    println!("A new content, sending it to the other side: {}!={}",s,current_content);
                    current_content = s.clone();
                    let data = s.as_bytes();
                    try!(writer.write_u32::<LittleEndian>(data.len() as u32));
                    try!(writer.write(data));
                }
                match rx.try_recv(){
                    Ok(v) =>{
                        println!("Received a new value for cb: {}", v );
                        if v != current_content{
                            current_content = v.clone();
                            match ctx.set_contents(v){
                                Ok(_) => println!("Seting the new value as clipboard content."),
                                Err(_) => println!("Failde to set the new value as clipboard content.")
                            }
                        }
                    }
                    Err(_) => ()
                }
            }
            Err(_) => ()
        };

    }
}

fn try_run_client(config : &Config) -> Result<()>{
    let mut stream = try!(TcpStream::connect((&config.outsideip as &str,config.port)));
    run_sync(&mut stream)
}

fn run_server(config : &Config) -> Result<()> {
    let listener = try!(TcpListener::bind((&config.localip as &str,config.port)));

    for stream in listener.incoming(){
        match stream{
            Ok(stream) => {
                thread::spawn(move||{
                    println!("connected");
                    run_sync(&mut stream.try_clone().unwrap())
                });
            }
            Err(e) => {
                println!("Connection failed {}",e );
            }
        }
    }

    drop(listener);
    Ok(())
}

fn main() {
    let mut config = Config{
        port: 24011,
        localip : "127.0.0.1".to_owned(),
        outsideip : "127.0.0.1".to_owned()
    };
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Synchronise clipboard content between two computers.");
        ap.refer(&mut config.port)
            .add_option(&["-p","--port"],Store,"Port address");
        ap.refer(&mut config.localip)
            .add_option(&["-l","--local"],Store,"Local ip address");
        ap.refer(&mut config.outsideip)
            .add_option(&["-o","--outside"],Store,"Outside ip address");
        ap.parse_args_or_exit();
    }
    println!("local: {}, outside: {}, port: {}",config.localip, config.outsideip, config.port );
    let res = match try_run_client(&config){
        Err(_) => {
            println!("Could not connect to server, creating own.");
            run_server(&config)},
        _ => Ok(())
    };

    match res {
        Ok(_) => println!("done"),
        Err(e) => println!("Failed: {}", e),
    }
}
<commit_msg>Fixed bug with adding data to vector instead of replacing it.<commit_after>extern crate clipboard;
extern crate byteorder;
extern crate argparse;

use clipboard::ClipboardContext;
use std::net::{TcpListener,TcpStream};
use std::io::{Error, ErrorKind, Result, Read,Write};
use std::thread;
use byteorder::{ReadBytesExt, WriteBytesExt,  LittleEndian};
use std::sync::mpsc::{Sender, channel};
use std::time::Duration;
use argparse::{ArgumentParser, Store};

struct Config {
    port : u16,
    localip : String,
    outsideip : String
}

fn run_reading(stream : &mut TcpStream,content : Sender<String>) -> Result<()>{
    loop {
        println!("Waiting for a data");
        let length : u32 = try!(stream.read_u32::<LittleEndian>());
        if length == 0{
            // ignore empty
            continue;
        }
        println!("Got the data size: {}", length);
        // let mut data = vec![0u8;length as usize];
        let mut data = vec![0u8;0];
        println!("");
        try!(stream.take(length as u64).read_to_end(&mut data));
        for v in data.iter(){
            print!("{} ",v);
        }
        match String::from_utf8(data){
            Ok(s) => {
                println!("Received new content from the other side: {}", s);
                let _ = content.send(s);
            }
            Err(_) => {
                println!("Failed to convert received content to string.");
            }
        };
    }
}

fn run_sync(stream : &mut TcpStream) -> Result<()>{
    let mut ctx = match ClipboardContext::new(){
        Ok(c) => c,
        Err(_) => return Err(Error::new(ErrorKind::Other,"Failed to create clipboard context"))
    };
    // println!("{}", ctx.get_contents().unwrap());
    //
    // ctx.set_contents("the new content".to_owned()).unwrap();
    let mut current_content = match ctx.get_contents(){
        Ok(s) => s,
        Err(_) => "".to_owned()
    };
    let (wx, rx) = channel::<String>();
    // // create thread which will read the stream for changes from outside, and apply changes to this system
    //
    let mut reading = stream.try_clone().unwrap();
    thread::spawn(move||{run_reading(&mut reading,wx)});
    let mut writer = stream.try_clone().unwrap();

    println!("initial clipboard value is: {}",current_content );

    loop{
        thread::sleep(Duration::new(1,0));
        println!("Checking for new content!");
        match ctx.get_contents(){
            Ok(s) => {

                if s != current_content{
                    println!("A new content, sending it to the other side: {}!={}",s,current_content);
                    current_content = s.clone();
                    let data = s.as_bytes();
                    for v in data.iter(){
                        print!("{} ",v );
                    }
                    println!("");
                    try!(writer.write_u32::<LittleEndian>(data.len() as u32));
                    try!(writer.write(data));
                }
                match rx.try_recv(){
                    Ok(v) =>{
                        println!("Received a new value for cb: {}", v );
                        if v != current_content{
                            current_content = v.clone();
                            match ctx.set_contents(v){
                                Ok(_) => println!("Seting the new value as clipboard content."),
                                Err(_) => println!("Failde to set the new value as clipboard content.")
                            }
                        }
                    }
                    Err(_) => ()
                }
            }
            Err(_) => ()
        };

    }
}

fn try_run_client(config : &Config) -> Result<()>{
    let mut stream = try!(TcpStream::connect((&config.outsideip as &str,config.port)));
    run_sync(&mut stream)
}

fn run_server(config : &Config) -> Result<()> {
    let listener = try!(TcpListener::bind((&config.localip as &str,config.port)));

    for stream in listener.incoming(){
        match stream{
            Ok(stream) => {
                thread::spawn(move||{
                    println!("connected");
                    run_sync(&mut stream.try_clone().unwrap())
                });
            }
            Err(e) => {
                println!("Connection failed {}",e );
            }
        }
    }

    drop(listener);
    Ok(())
}

fn main() {
    let mut config = Config{
        port: 24011,
        localip : "127.0.0.1".to_owned(),
        outsideip : "127.0.0.1".to_owned()
    };
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Synchronise clipboard content between two computers.");
        ap.refer(&mut config.port)
            .add_option(&["-p","--port"],Store,"Port address");
        ap.refer(&mut config.localip)
            .add_option(&["-l","--local"],Store,"Local ip address");
        ap.refer(&mut config.outsideip)
            .add_option(&["-o","--outside"],Store,"Outside ip address");
        ap.parse_args_or_exit();
    }
    println!("local: {}, outside: {}, port: {}",config.localip, config.outsideip, config.port );
    let res = match try_run_client(&config){
        Err(_) => {
            println!("Could not connect to server, creating own.");
            run_server(&config)},
        _ => Ok(())
    };

    match res {
        Ok(_) => println!("done"),
        Err(e) => println!("Failed: {}", e),
    }
}
<|endoftext|>


<commit_before>#![warn(deprecated_in_future)]
#![warn(future_incompatible)]
#![warn(nonstandard_style)]
#![warn(rust_2018_compatibility)]
#![warn(rust_2018_idioms)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused)]

#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::enum_glob_use)]
#![allow(clippy::map_unwrap_or)]
#![allow(clippy::match_same_arms)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::non_ascii_literal)]
#![allow(clippy::option_if_let_else)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::unnested_or_patterns)] // TODO: remove this when we support Rust 1.53.0
#![allow(clippy::unused_self)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::wildcard_imports)]

use std::env;
use std::ffi::{OsStr, OsString};
use std::io::{self, Write, ErrorKind};
use std::path::{Component, PathBuf};

use ansi_term::{ANSIStrings, Style};

use log::*;

use crate::fs::{Dir, File};
use crate::fs::feature::git::GitCache;
use crate::fs::filter::GitIgnore;
use crate::options::{Options, Vars, vars, OptionsResult};
use crate::output::{escape, lines, grid, grid_details, details, View, Mode};
use crate::theme::Theme;

mod fs;
mod info;
mod logger;
mod options;
mod output;
mod theme;

fn main() {
    use std::process::exit;

    unsafe {
        libc::signal(libc::SIGPIPE, libc::SIG_DFL);
    }

    logger::configure(env::var_os(vars::EXA_DEBUG));

    let args: Vec<_> = env::args_os().skip(1).collect();
    match Options::parse(args.iter().map(|e| e.as_ref()), &LiveVars) {
        OptionsResult::Ok(options, mut input_paths) => {

            // List the current directory by default.
            // (This has to be done here, otherwise git_options won’t see it.)
            if input_paths.is_empty() {
                input_paths = vec![ OsStr::new(".") ];
            }

            let git = git_options(&options, &input_paths);
            let writer = io::stdout();

            let console_width = options.view.width.actual_terminal_width();
            let theme = options.theme.to_theme(console_width.is_some());
            let exa = Exa { options, writer, input_paths, theme, console_width, git };

            match exa.run() {
                Ok(exit_status) => {
                    exit(exit_status);
                }

                Err(e) if e.kind() == ErrorKind::BrokenPipe => {
                    warn!("Broken pipe error: {}", e);
                    exit(exits::SUCCESS);
                }

                Err(e) => {
                    eprintln!("{}", e);
                    exit(exits::RUNTIME_ERROR);
                }
            }
        }

        OptionsResult::Help(help_text) => {
            print!("{}", help_text);
        }

        OptionsResult::Version(version_str) => {
            print!("{}", version_str);
        }

        OptionsResult::InvalidOptions(error) => {
            eprintln!("exa: {}", error);

            if let Some(s) = error.suggestion() {
                eprintln!("{}", s);
            }

            exit(exits::OPTIONS_ERROR);
        }
    }
}

/// The main program wrapper.
pub struct Exa<'args> {

    /// List of command-line options, having been successfully parsed.
    pub options: Options,

    /// The output handle that we write to.
    pub writer: io::Stdout,

    /// List of the free command-line arguments that should correspond to file
    /// names (anything that isn’t an option).
    pub input_paths: Vec<&'args OsStr>,

    /// The theme that has been configured from the command-line options and
    /// environment variables. If colours are disabled, this is a theme with
    /// every style set to the default.
    pub theme: Theme,

    /// The detected width of the console. This is used to determine which
    /// view to use.
    pub console_width: Option<usize>,

    /// A global Git cache, if the option was passed in.
    /// This has to last the lifetime of the program, because the user might
    /// want to list several directories in the same repository.
    pub git: Option<GitCache>,
}

/// The “real” environment variables type.
/// Instead of just calling `var_os` from within the options module,
/// the method of looking up environment variables has to be passed in.
struct LiveVars;
impl Vars for LiveVars {
    fn get(&self, name: &'static str) -> Option<OsString> {
        env::var_os(name)
    }
}

/// Create a Git cache populated with the arguments that are going to be
/// listed before they’re actually listed, if the options demand it.
fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
    if options.should_scan_for_git() {
        Some(args.iter().map(PathBuf::from).collect())
    }
    else {
        None
    }
}

impl<'args> Exa<'args> {
    /// # Errors
    ///
    /// Will return `Err` if printing to stderr fails.
    pub fn run(mut self) -> io::Result<i32> {
        debug!("Running with options: {:#?}", self.options);

        let mut files = Vec::new();
        let mut dirs = Vec::new();
        let mut exit_status = 0;

        for file_path in &self.input_paths {
            match File::from_args(PathBuf::from(file_path), None, None) {
                Err(e) => {
                    exit_status = 2;
                    writeln!(io::stderr(), "{:?}: {}", file_path, e)?;
                }

                Ok(f) => {
                    if f.points_to_directory() && ! self.options.dir_action.treat_dirs_as_files() {
                        match f.to_dir() {
                            Ok(d)   => dirs.push(d),
                            Err(e)  => writeln!(io::stderr(), "{:?}: {}", file_path, e)?,
                        }
                    }
                    else {
                        files.push(f);
                    }
                }
            }
        }

        // We want to print a directory’s name before we list it, *except* in
        // the case where it’s the only directory, *except* if there are any
        // files to print as well. (It’s a double negative)

        let no_files = files.is_empty();
        let is_only_dir = dirs.len() == 1 && no_files;

        self.options.filter.filter_argument_files(&mut files);
        self.print_files(None, files)?;

        self.print_dirs(dirs, no_files, is_only_dir, exit_status)
    }

    fn print_dirs(&mut self, dir_files: Vec<Dir>, mut first: bool, is_only_dir: bool, exit_status: i32) -> io::Result<i32> {
        for dir in dir_files {

            // Put a gap between directories, or between the list of files and
            // the first directory.
            if first {
                first = false;
            }
            else {
                writeln!(&mut self.writer)?;
            }

            if ! is_only_dir {
                let mut bits = Vec::new();
                escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
                writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?;
            }

            let mut children = Vec::new();
            let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
            for file in dir.files(self.options.filter.dot_filter, self.git.as_ref(), git_ignore) {
                match file {
                    Ok(file)        => children.push(file),
                    Err((path, e))  => writeln!(io::stderr(), "[{}: {}]", path.display(), e)?,
                }
            };

            self.options.filter.filter_child_files(&mut children);
            self.options.filter.sort_files(&mut children);

            if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
                let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;
                if ! recurse_opts.tree && ! recurse_opts.is_too_deep(depth) {

                    let mut child_dirs = Vec::new();
                    for child_dir in children.iter().filter(|f| f.is_directory() && ! f.is_all_all) {
                        match child_dir.to_dir() {
                            Ok(d)   => child_dirs.push(d),
                            Err(e)  => writeln!(io::stderr(), "{}: {}", child_dir.path.display(), e)?,
                        }
                    }

                    self.print_files(Some(&dir), children)?;
                    match self.print_dirs(child_dirs, false, false, exit_status) {
                        Ok(_)   => (),
                        Err(e)  => return Err(e),
                    }
                    continue;
                }
            }

            self.print_files(Some(&dir), children)?;
        }

        Ok(exit_status)
    }

    /// Prints the list of files using whichever view is selected.
    fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File<'_>>) -> io::Result<()> {
        if files.is_empty() {
            return Ok(());
        }

        let theme = &self.theme;
        let View { ref mode, ref file_style, .. } = self.options.view;

        match (mode, self.console_width) {
            (Mode::Grid(ref opts), Some(console_width)) => {
                let filter = &self.options.filter;
                let r = grid::Render { files, theme, file_style, opts, console_width, filter };
                r.render(&mut self.writer)
            }

            (Mode::Grid(_), None) |
            (Mode::Lines,   _)    => {
                let filter = &self.options.filter;
                let r = lines::Render { files, theme, file_style, filter };
                r.render(&mut self.writer)
            }

            (Mode::Details(ref opts), _) => {
                let filter = &self.options.filter;
                let recurse = self.options.dir_action.recurse_options();

                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
                let git = self.git.as_ref();
                let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };
                r.render(&mut self.writer)
            }

            (Mode::GridDetails(ref opts), Some(console_width)) => {
                let grid = &opts.grid;
                let details = &opts.details;
                let row_threshold = opts.row_threshold;

                let filter = &self.options.filter;
                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
                let git = self.git.as_ref();

                let r = grid_details::Render { dir, files, theme, file_style, grid, details, filter, row_threshold, git_ignoring, git, console_width };
                r.render(&mut self.writer)
            }

            (Mode::GridDetails(ref opts), None) => {
                let opts = &opts.to_details_options();
                let filter = &self.options.filter;
                let recurse = self.options.dir_action.recurse_options();
                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;

                let git = self.git.as_ref();
                let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };
                r.render(&mut self.writer)
            }
        }
    }
}

mod exits {

    /// Exit code for when exa runs OK.
    pub const SUCCESS: i32 = 0;

    /// Exit code for when there was at least one I/O error during execution.
    pub const RUNTIME_ERROR: i32 = 1;

    /// Exit code for when the command-line options are invalid.
    pub const OPTIONS_ERROR: i32 = 3;
}
<commit_msg>src/main.rs: remove clippy::unnested_or_patterns<commit_after>#![warn(deprecated_in_future)]
#![warn(future_incompatible)]
#![warn(nonstandard_style)]
#![warn(rust_2018_compatibility)]
#![warn(rust_2018_idioms)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused)]

#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::enum_glob_use)]
#![allow(clippy::map_unwrap_or)]
#![allow(clippy::match_same_arms)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::non_ascii_literal)]
#![allow(clippy::option_if_let_else)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::unused_self)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::wildcard_imports)]

use std::env;
use std::ffi::{OsStr, OsString};
use std::io::{self, Write, ErrorKind};
use std::path::{Component, PathBuf};

use ansi_term::{ANSIStrings, Style};

use log::*;

use crate::fs::{Dir, File};
use crate::fs::feature::git::GitCache;
use crate::fs::filter::GitIgnore;
use crate::options::{Options, Vars, vars, OptionsResult};
use crate::output::{escape, lines, grid, grid_details, details, View, Mode};
use crate::theme::Theme;

mod fs;
mod info;
mod logger;
mod options;
mod output;
mod theme;

fn main() {
    use std::process::exit;

    unsafe {
        libc::signal(libc::SIGPIPE, libc::SIG_DFL);
    }

    logger::configure(env::var_os(vars::EXA_DEBUG));

    let args: Vec<_> = env::args_os().skip(1).collect();
    match Options::parse(args.iter().map(|e| e.as_ref()), &LiveVars) {
        OptionsResult::Ok(options, mut input_paths) => {

            // List the current directory by default.
            // (This has to be done here, otherwise git_options won’t see it.)
            if input_paths.is_empty() {
                input_paths = vec![ OsStr::new(".") ];
            }

            let git = git_options(&options, &input_paths);
            let writer = io::stdout();

            let console_width = options.view.width.actual_terminal_width();
            let theme = options.theme.to_theme(console_width.is_some());
            let exa = Exa { options, writer, input_paths, theme, console_width, git };

            match exa.run() {
                Ok(exit_status) => {
                    exit(exit_status);
                }

                Err(e) if e.kind() == ErrorKind::BrokenPipe => {
                    warn!("Broken pipe error: {}", e);
                    exit(exits::SUCCESS);
                }

                Err(e) => {
                    eprintln!("{}", e);
                    exit(exits::RUNTIME_ERROR);
                }
            }
        }

        OptionsResult::Help(help_text) => {
            print!("{}", help_text);
        }

        OptionsResult::Version(version_str) => {
            print!("{}", version_str);
        }

        OptionsResult::InvalidOptions(error) => {
            eprintln!("exa: {}", error);

            if let Some(s) = error.suggestion() {
                eprintln!("{}", s);
            }

            exit(exits::OPTIONS_ERROR);
        }
    }
}

/// The main program wrapper.
pub struct Exa<'args> {

    /// List of command-line options, having been successfully parsed.
    pub options: Options,

    /// The output handle that we write to.
    pub writer: io::Stdout,

    /// List of the free command-line arguments that should correspond to file
    /// names (anything that isn’t an option).
    pub input_paths: Vec<&'args OsStr>,

    /// The theme that has been configured from the command-line options and
    /// environment variables. If colours are disabled, this is a theme with
    /// every style set to the default.
    pub theme: Theme,

    /// The detected width of the console. This is used to determine which
    /// view to use.
    pub console_width: Option<usize>,

    /// A global Git cache, if the option was passed in.
    /// This has to last the lifetime of the program, because the user might
    /// want to list several directories in the same repository.
    pub git: Option<GitCache>,
}

/// The “real” environment variables type.
/// Instead of just calling `var_os` from within the options module,
/// the method of looking up environment variables has to be passed in.
struct LiveVars;
impl Vars for LiveVars {
    fn get(&self, name: &'static str) -> Option<OsString> {
        env::var_os(name)
    }
}

/// Create a Git cache populated with the arguments that are going to be
/// listed before they’re actually listed, if the options demand it.
fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
    if options.should_scan_for_git() {
        Some(args.iter().map(PathBuf::from).collect())
    }
    else {
        None
    }
}

impl<'args> Exa<'args> {
    /// # Errors
    ///
    /// Will return `Err` if printing to stderr fails.
    pub fn run(mut self) -> io::Result<i32> {
        debug!("Running with options: {:#?}", self.options);

        let mut files = Vec::new();
        let mut dirs = Vec::new();
        let mut exit_status = 0;

        for file_path in &self.input_paths {
            match File::from_args(PathBuf::from(file_path), None, None) {
                Err(e) => {
                    exit_status = 2;
                    writeln!(io::stderr(), "{:?}: {}", file_path, e)?;
                }

                Ok(f) => {
                    if f.points_to_directory() && ! self.options.dir_action.treat_dirs_as_files() {
                        match f.to_dir() {
                            Ok(d)   => dirs.push(d),
                            Err(e)  => writeln!(io::stderr(), "{:?}: {}", file_path, e)?,
                        }
                    }
                    else {
                        files.push(f);
                    }
                }
            }
        }

        // We want to print a directory’s name before we list it, *except* in
        // the case where it’s the only directory, *except* if there are any
        // files to print as well. (It’s a double negative)

        let no_files = files.is_empty();
        let is_only_dir = dirs.len() == 1 && no_files;

        self.options.filter.filter_argument_files(&mut files);
        self.print_files(None, files)?;

        self.print_dirs(dirs, no_files, is_only_dir, exit_status)
    }

    fn print_dirs(&mut self, dir_files: Vec<Dir>, mut first: bool, is_only_dir: bool, exit_status: i32) -> io::Result<i32> {
        for dir in dir_files {

            // Put a gap between directories, or between the list of files and
            // the first directory.
            if first {
                first = false;
            }
            else {
                writeln!(&mut self.writer)?;
            }

            if ! is_only_dir {
                let mut bits = Vec::new();
                escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
                writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?;
            }

            let mut children = Vec::new();
            let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
            for file in dir.files(self.options.filter.dot_filter, self.git.as_ref(), git_ignore) {
                match file {
                    Ok(file)        => children.push(file),
                    Err((path, e))  => writeln!(io::stderr(), "[{}: {}]", path.display(), e)?,
                }
            };

            self.options.filter.filter_child_files(&mut children);
            self.options.filter.sort_files(&mut children);

            if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
                let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;
                if ! recurse_opts.tree && ! recurse_opts.is_too_deep(depth) {

                    let mut child_dirs = Vec::new();
                    for child_dir in children.iter().filter(|f| f.is_directory() && ! f.is_all_all) {
                        match child_dir.to_dir() {
                            Ok(d)   => child_dirs.push(d),
                            Err(e)  => writeln!(io::stderr(), "{}: {}", child_dir.path.display(), e)?,
                        }
                    }

                    self.print_files(Some(&dir), children)?;
                    match self.print_dirs(child_dirs, false, false, exit_status) {
                        Ok(_)   => (),
                        Err(e)  => return Err(e),
                    }
                    continue;
                }
            }

            self.print_files(Some(&dir), children)?;
        }

        Ok(exit_status)
    }

    /// Prints the list of files using whichever view is selected.
    fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File<'_>>) -> io::Result<()> {
        if files.is_empty() {
            return Ok(());
        }

        let theme = &self.theme;
        let View { ref mode, ref file_style, .. } = self.options.view;

        match (mode, self.console_width) {
            (Mode::Grid(ref opts), Some(console_width)) => {
                let filter = &self.options.filter;
                let r = grid::Render { files, theme, file_style, opts, console_width, filter };
                r.render(&mut self.writer)
            }

            (Mode::Grid(_), None) |
            (Mode::Lines,   _)    => {
                let filter = &self.options.filter;
                let r = lines::Render { files, theme, file_style, filter };
                r.render(&mut self.writer)
            }

            (Mode::Details(ref opts), _) => {
                let filter = &self.options.filter;
                let recurse = self.options.dir_action.recurse_options();

                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
                let git = self.git.as_ref();
                let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };
                r.render(&mut self.writer)
            }

            (Mode::GridDetails(ref opts), Some(console_width)) => {
                let grid = &opts.grid;
                let details = &opts.details;
                let row_threshold = opts.row_threshold;

                let filter = &self.options.filter;
                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
                let git = self.git.as_ref();

                let r = grid_details::Render { dir, files, theme, file_style, grid, details, filter, row_threshold, git_ignoring, git, console_width };
                r.render(&mut self.writer)
            }

            (Mode::GridDetails(ref opts), None) => {
                let opts = &opts.to_details_options();
                let filter = &self.options.filter;
                let recurse = self.options.dir_action.recurse_options();
                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;

                let git = self.git.as_ref();
                let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };
                r.render(&mut self.writer)
            }
        }
    }
}

mod exits {

    /// Exit code for when exa runs OK.
    pub const SUCCESS: i32 = 0;

    /// Exit code for when there was at least one I/O error during execution.
    pub const RUNTIME_ERROR: i32 = 1;

    /// Exit code for when the command-line options are invalid.
    pub const OPTIONS_ERROR: i32 = 3;
}
<|endoftext|>


<commit_before>extern crate gtk;
extern crate nix;
mod battery;

use std::env;

use gtk::prelude::*;
use gtk::{ButtonsType, DialogFlags, MessageType, MessageDialog, Window};

use nix::unistd::{fork, ForkResult};

use battery::Battery;
use battery::BatteryStatus;

fn console_output(bat: &Battery) {
    // Icons
    let plug          = '\u{f1e6}';
    let check         = '\u{f00c}';
    let full          = '\u{f240}';
    let three_quarter = '\u{f241}';
    let half          = '\u{f242}';
    let quarter       = '\u{f243}';
    let empty         = '\u{f244}';

    // Colors
    let green  = "#859900";
    let cyan   = "#2AA198";
    let yellow = "#B58900";
    let orange = "#CB4B16";
    let red    = "#DC322F";

    let mut percent = bat.percent_remaining();

    let (mut icon, mut color) = match percent {
        0  ... 10  => (empty, red),
        11 ... 35  => (quarter, orange),
        36 ... 60  => (half, yellow),
        61 ... 85  => (three_quarter, cyan),
        86 ... 100 => (full, green),
        _          => std::process::exit(1),
    };

    match bat.charge_status {
        BatteryStatus::FULL => {
            icon = check;
            color = green;
            percent = 100;
        },
        BatteryStatus::CHARGING => {
            icon = plug;
        },
        _ => (),
    };

    if bat.charge_status == BatteryStatus::FULL {
        color = green;
        percent = 100;
    }

    let out = format!("{} {}%\n",
                      color, icon, percent);
    print!("{0:}{0:}", out);
    if percent <= 5 {
        std::process::exit(33);
    }
}

fn show_gtk_dialog(bat: &Battery) {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        return;
    }

    let body_string = format!("Battery name:\t\t{}\n\
                              Battery charge:\t\t{}\n\
                              Charge when full:\t{}\n\
                              Design full:\t\t\t{}\n\
                              Cycle count:\t\t{}\n\
                              Charging status:\t{}\n\
                              Current now:\t\t{}\n\
                              Current avg:\t\t{}\n\
                              % Charged:\t\t\t{}%\n\
                              Time remaining:\t\t{}\n\
                              Battery health:\t\t{}%\n\
                              % Charged (abs):\t{}%\n",
                              bat.name, bat.charge_now, bat.charge_full,
                              bat.charge_full_design, bat.cycle_count,
                              bat.charge_status, bat.current_now,
                              bat.current_avg, bat.percent_remaining(),
                              bat.time_remaining(), bat.health(),
                              bat.abs_percent_remaining());

    MessageDialog::new(None::<&Window>,
                       DialogFlags::empty(),
                       MessageType::Info,
                       ButtonsType::Ok,
                       &body_string).run();
    std::process::exit(0);
}

fn get_env_var(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(val) => {
            if val.is_empty() {
                None
            } else {
                Some(val)
            }
        },
        Err(_) => None
    }
}

fn main() {
    let battery_name = match get_env_var("BLOCK_INSTANCE") {
        Some(val) => val,
        None => "BAT0".to_owned(),
    };

    let bat = Battery::initialize(&battery_name);

    let button: u32 = match get_env_var("BLOCK_BUTTON") {
        Some(val) => val.trim().parse().unwrap(),
        None => 0
    };

    /*
     * For the purposes of i3blocks, 3 corresponds to a right click. When
     * that happens, fork() and then show a dialog. If we don't fork(), then
     * the dialog prevents this process from exiting and the block doesn't
     * update. The printing to the console is done in the parent thread.
     * It is the responsibility of the child process to exit after displaying
     * its dialog.
     */
    if button == 3 {
        match fork() {
            Ok(ForkResult::Parent { child, .. }) => (),
            Ok(ForkResult::Child) => show_gtk_dialog(&bat),
            Err(_) => println!("Fork failed"),
        }
    }
    console_output(&bat);
}
<commit_msg>Document that show_gtk_dialog should not return<commit_after>extern crate gtk;
extern crate nix;
mod battery;

use std::env;

use gtk::prelude::*;
use gtk::{ButtonsType, DialogFlags, MessageType, MessageDialog, Window};

use nix::unistd::{fork, ForkResult};

use battery::Battery;
use battery::BatteryStatus;

fn console_output(bat: &Battery) {
    // Icons
    let plug          = '\u{f1e6}';
    let check         = '\u{f00c}';
    let full          = '\u{f240}';
    let three_quarter = '\u{f241}';
    let half          = '\u{f242}';
    let quarter       = '\u{f243}';
    let empty         = '\u{f244}';

    // Colors
    let green  = "#859900";
    let cyan   = "#2AA198";
    let yellow = "#B58900";
    let orange = "#CB4B16";
    let red    = "#DC322F";

    let mut percent = bat.percent_remaining();

    let (mut icon, mut color) = match percent {
        0  ... 10  => (empty, red),
        11 ... 35  => (quarter, orange),
        36 ... 60  => (half, yellow),
        61 ... 85  => (three_quarter, cyan),
        86 ... 100 => (full, green),
        _          => std::process::exit(1),
    };

    match bat.charge_status {
        BatteryStatus::FULL => {
            icon = check;
            color = green;
            percent = 100;
        },
        BatteryStatus::CHARGING => {
            icon = plug;
        },
        _ => (),
    };

    if bat.charge_status == BatteryStatus::FULL {
        color = green;
        percent = 100;
    }

    let out = format!("{} {}%\n",
                      color, icon, percent);
    print!("{0:}{0:}", out);
    if percent <= 5 {
        std::process::exit(33);
    }
}

/*
 * Displays the GTK MessageDialog with information about the current status of
 * the battery. This contains all the fields that this program collects
 * information on.
 *
 * This function should never return. It should always exit the current
 * process.
 */
fn show_gtk_dialog(bat: &Battery) {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        std::process::exit(1);
    }

    let body_string = format!("Battery name:\t\t{}\n\
                              Battery charge:\t\t{}\n\
                              Charge when full:\t{}\n\
                              Design full:\t\t\t{}\n\
                              Cycle count:\t\t{}\n\
                              Charging status:\t{}\n\
                              Current now:\t\t{}\n\
                              Current avg:\t\t{}\n\
                              % Charged:\t\t\t{}%\n\
                              Time remaining:\t\t{}\n\
                              Battery health:\t\t{}%\n\
                              % Charged (abs):\t{}%\n",
                              bat.name, bat.charge_now, bat.charge_full,
                              bat.charge_full_design, bat.cycle_count,
                              bat.charge_status, bat.current_now,
                              bat.current_avg, bat.percent_remaining(),
                              bat.time_remaining(), bat.health(),
                              bat.abs_percent_remaining());

    MessageDialog::new(None::<&Window>,
                       DialogFlags::empty(),
                       MessageType::Info,
                       ButtonsType::Ok,
                       &body_string).run();
    std::process::exit(0);
}

fn get_env_var(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(val) => {
            if val.is_empty() {
                None
            } else {
                Some(val)
            }
        },
        Err(_) => None
    }
}

fn main() {
    let battery_name = match get_env_var("BLOCK_INSTANCE") {
        Some(val) => val,
        None => "BAT0".to_owned(),
    };

    let bat = Battery::initialize(&battery_name);

    let button: u32 = match get_env_var("BLOCK_BUTTON") {
        Some(val) => val.trim().parse().unwrap(),
        None => 0
    };

    /*
     * For the purposes of i3blocks, 3 corresponds to a right click. When
     * that happens, fork() and then show a dialog. If we don't fork(), then
     * the dialog prevents this process from exiting and the block doesn't
     * update. The printing to the console is done in the parent thread.
     * It is the responsibility of the child process to exit after displaying
     * its dialog.
     */
    if button == 3 {
        match fork() {
            Ok(ForkResult::Parent { child, .. }) => (),
            Ok(ForkResult::Child) => show_gtk_dialog(&bat),
            Err(_) => println!("Fork failed"),
        }
    }
    console_output(&bat);
}
<|endoftext|>