diff --git a/Cargo.lock b/Cargo.lock index 06be72e..2f59e2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,7 @@ checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" name = "mapcat" version = "0.1.0" dependencies = [ + "libc", "memmap", "num_cpus", "rustc_version", diff --git a/Cargo.toml b/Cargo.toml index 69759a1..1ada125 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -mapcat = { path = "." } +libc = "0.2.81" memmap = "0.7.0" num_cpus = "1.13.0" smallvec = "1.5.1" diff --git a/src/main.rs b/src/main.rs index c7b5f29..2231a76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,17 +26,18 @@ mod map; mod job; mod work; mod arg; +mod open; mod consts; - - fn work(arg::Operation{output, inputs}: arg::Operation) -> Result<(), Box> { // todo: // - create and open output file + let output = open::output(output)?; // - open and stat all input files in order (`job::create_from_file`). // - `fallocate` the output file fd to the sum of all input file sizes + // - `mmap` the output file as writable // // - spawn the task thread pool diff --git a/src/map.rs b/src/map.rs index 0edd4ee..7ef2007 100644 --- a/src/map.rs +++ b/src/map.rs @@ -46,6 +46,17 @@ impl MemoryMapMut { self.as_ref() } + pub fn map(file: fs::File) -> io::Result + { + Ok(Self{ + map: unsafe{ MmapMut::map_mut(&file)? }, + file, + }) + } + #[inline] pub fn len(&self) -> usize + { + self.map.len() + } pub fn open(from: impl AsRef) -> io::Result { let file = fs::OpenOptions::new() diff --git a/src/open.rs b/src/open.rs new file mode 100644 index 0000000..f5a47f3 --- /dev/null +++ b/src/open.rs @@ -0,0 +1,101 @@ +use super::*; +use std::{ + fs,io, + path::Path, + fmt, error, +}; +use fs::File; +use std::borrow::Cow; +use std::os::unix::io::AsRawFd as _; + +fn fallocate(file: &mut File, to: usize) -> Result<(), OutputError> +{ + let fd = file.as_raw_fd(); + unsafe { + if libc::fallocate(fd, 0, 0, to as libc::off_t) == 0 { + Ok(()) + } else { + let errno = *libc::__errno_location(); + Err(OutputError::Allocate(errno)) + } + } +} + +/// A reserved output file. +#[derive(Debug)] +pub struct Output(File); + +impl Output +{ + /// Allocate this reserved output file `to` bytes, the map it and return. + pub fn complete(self, to: usize) -> Result + { + let mut file = self.0; + fallocate(&mut file, to)?; + map::MemoryMapMut::map(file) + .map_err(OutputError::Map) + .and_then(|map| if map.len() != to { + Err(OutputError::Size{expected: to, got: map.len()}) + } else { + Ok(map) + }) + } +} + +/// Create a reserved output fd +#[inline] pub fn output(path: impl AsRef) -> Result +{ + fs::OpenOptions::new() + .create(true) + .write(true) + .open(path) + .map(Output) + .map_err(OutputError::Open) +} + +fn errno_str<'a>(errno: libc::c_int) -> Cow<'a, str> +{ + unsafe { + let ptr = libc::strerror(errno) as *const libc::c_char; + if ptr.is_null() { + return Cow::Borrowed("Unknown"); + } + let cstr = std::ffi::CStr::from_ptr(ptr); + cstr.to_string_lossy() + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum OutputError { + Open(io::Error), + Allocate(libc::c_int), + Map(io::Error), + Size{expected: usize, got: usize}, +} +impl error::Error for OutputError +{ + fn source(&self) -> Option<&(dyn error::Error + 'static)> + { + match &self { + Self::Open(io) + | Self::Map(io) + => Some(io), + _ => None, + } + } +} +impl fmt::Display for OutputError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self { + Self::Open(io) => write!(f, "Failed to open file: {}", io), + Self::Allocate(errno) => write!(f, "fallocate() failed with error {}: {}", errno, errno_str(*errno)), + Self::Map(io) => write!(f, "mmap() failed: {}", io), + Self::Size{expected, got} => write!(f, "mapped file size mismatch: expected {}, got {}", expected, got), + } + } +} + +