diff --git a/fcmprs/Cargo.toml b/fcmprs/Cargo.toml index 0e27425..6f06b50 100644 --- a/fcmprs/Cargo.toml +++ b/fcmprs/Cargo.toml @@ -7,3 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +memmap = "0.7.0" +rayon = "1.5.0" +smallvec = "1.5.0" diff --git a/fcmprs/src/error.rs b/fcmprs/src/error.rs new file mode 100644 index 0000000..c4041dc --- /dev/null +++ b/fcmprs/src/error.rs @@ -0,0 +1,20 @@ + + +pub trait ResultPrintExt +{ + fn discard_msg(self, msg: impl AsRef) -> Option; +} + +impl ResultPrintExt for Result + where E: std::fmt::Display +{ + fn discard_msg(self, msg: impl AsRef) -> Option { + match self { + Ok(v) => Some(v), + Err(e) => { + eprintln!("{}: {}", msg.as_ref(), e); + None + }, + } + } +} diff --git a/fcmprs/src/main.rs b/fcmprs/src/main.rs index e7a11a9..4292a95 100644 --- a/fcmprs/src/main.rs +++ b/fcmprs/src/main.rs @@ -1,3 +1,61 @@ +use rayon::prelude::*; +use std::{ + path::Path, + io, fs::{self, OpenOptions,}, +}; +use smallvec::SmallVec; + +fn usage() -> ! +{ + eprintln!("fcmprs: Compare files for identity"); + eprintln!("Usage: {} ", std::env::args().next().unwrap()); + + std::process::exit(-1) +} + +mod error; +use error::ResultPrintExt as _; + +mod map; + fn main() { - println!("Hello, world!"); + let (map1, rest) = { + let mut args = std::env::args().skip(1); + if let Some(one) = args.next() { + (one, args) + } else { + usage(); + } + }; + + std::process::exit(dbg!(if let Some(map1) = map::map(&map1).discard_msg(format!("Failed to map file {}", map1)) { + let slice = map1.as_slice(); + let mut ok = true; + let chk: SmallVec<[_; 32]> = rest.filter_map(|filename| { + let path = Path::new(&filename); + if path.exists() && path.is_file() { + map::map(path).discard_msg(format!("Failed to map file {}", filename)) + .or_else(|| { ok = false; None }) + } else { + eprintln!("File {} does not exist or is not a normal file", filename); + ok = false; + None + } + }).collect(); + + if !ok { + -1 + } else { + match chk.into_par_iter() + .map(|map| slice == map.as_slice()) + .reduce_with(|x, y| x && y) + { + Some(true) => 0, + Some(false) => 1, + None => usage(), + } + } + } else { + -1 + })) } diff --git a/fcmprs/src/map.rs b/fcmprs/src/map.rs new file mode 100644 index 0000000..6e8e5c6 --- /dev/null +++ b/fcmprs/src/map.rs @@ -0,0 +1,30 @@ +use super::*; + +/// Represents an open and memory mapped file +#[derive(Debug)] +pub struct MemMap +{ + map: memmap::Mmap, + file: fs::File, +} + +impl MemMap +{ + /// Get the memory mapped portion as a slice + #[inline] pub fn as_slice(&self) -> &[u8] + { + &self.map[..] + } +} + +/// Attempt to map this file +pub fn map(file: impl AsRef) -> io::Result +{ + let file = OpenOptions::new() + .read(true) + .open(file)?; + Ok(MemMap { + map: unsafe { memmap::Mmap::map(&file)? }, + file, + }) +}