From 03b33e6472083f74413b7d1d03137eff506f7044 Mon Sep 17 00:00:00 2001 From: Avril Date: Wed, 8 Feb 2023 01:02:57 +0000 Subject: [PATCH] Tested mapping modes "Input" and "Both" work. (And I think "neither" does too?) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added feature `unsafe-mappings` which allows forceful mappings of output files; this is dangerous however as it cannot respect `>>` vs. `>`, so it"s its own feature. Fortune for chacha20's current commit: Future curse − 末凶 --- Cargo.toml | 4 +++ src/main.rs | 17 ++++----- src/mapped.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 101 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 59e70ad..e804d68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,10 @@ default = ["mmap"] # Try to map inputs/outputs before using buffers mmap = ["libc"] +# Forcefully map all output real files. +# This is unsafe because we cannot distinguish the offset at which to map the file descriptor, or if there even is one. +unsafe-mappings = ["mmap"] + # Explicitly clear buffers and cache after use explicit_clear = [] diff --git a/src/main.rs b/src/main.rs index e613cbd..5570b61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,34 +93,35 @@ const USE_MMAP: bool = if cfg!(feature="mmap") { mod mapped; #[allow(unreachable_code)] -fn try_mmap(decrypt: bool, key: Key, iv: IV) -> std::io::Result +fn try_mmap(decrypt: bool, key: Key, iv: IV) -> Result { #[cfg(feature="mmap")] return mapped::try_process(if decrypt { cha::decrypter(key, iv).expect("Failed to create decrypter") } else { cha::encrypter(key, iv).expect("Failed to create encrypter") - }).map(|_| 0); + }).map(|_| 0i32); unreachable!("Built without feature `mmap`, but still tried to call into it. This is a bug") } fn main() { - let (mode, key, iv) = keys().expect("Failed to read keys from argv (base64)"); - let stdout = std::io::stdout(); - let input = std::io::stdin(); - // Attempt a mapped solution if USE_MMAP && mode != Mode::Keygen { match try_mmap(mode == Mode::Decrypt, key, iv) { Ok(0) => return, Ok(n) => std::process::exit(n), Err(err) => if cfg!(debug_assertions) { - eprintln!("Failed to mmap input or output for processing, falling back to stream: {}", err); + eprintln!("Failed to mmap input or output for processing, falling back to stream: {}", &err); + eprintln!("\t{:?}", err); } } - } + } + + let stdout = std::io::stdout(); + let input = std::io::stdin(); + // Streaming use std::io::Write; match mode diff --git a/src/mapped.rs b/src/mapped.rs index 8b2043e..63e4b29 100644 --- a/src/mapped.rs +++ b/src/mapped.rs @@ -169,10 +169,12 @@ fn translate_hugetlb_size(size: usize) -> mapped_file::hugetlb::HugePage } const MB: usize = 1024*1024; const GB: usize = 1024 * MB; + + #[allow(overlapping_range_endpoints)] match size { 0..=MB => mapped_file::hugetlb::HugePage::Smallest, MB..=GB => mapped_file::hugetlb::HugePage::Selected(check_func), - very_high => mapped_file::hugetlb::HugePage::Largest, + _very_high => mapped_file::hugetlb::HugePage::Largest, } } @@ -195,7 +197,7 @@ fn create_sized_temp_mapping_at(size: usize) -> io::Result<(Ma { const HUGETLB_THRESH: usize = 1024 * 1024; // 1MB let file = match size { - 0..=HUGETLB_THRESH => MemoryFile::with_size(size), + 1..=HUGETLB_THRESH => MemoryFile::with_size(size), size if HUGE => { let hugetlb = translate_hugetlb_size(size); let file = if let Some(flag) =hugetlb.compute_huge() { @@ -221,6 +223,7 @@ type MappedMemoryFile = MappedFile; /// How a mapped operation should optimally take place //TODO: Make optimised mapping operations for each one, like `fn process(self, enc: &mut Crypter) -> io::Result` +#[derive(Debug)] pub enum OpTable { /// Both input and output are mapped @@ -383,10 +386,23 @@ fn sized_then_or(stream: T, trans: F) -> Result #[inline] fn map_size_or(stream: T, size: usize, trans: F) -> Result - where F: FnOnce(MappedFile, usize) -> U +where F: FnOnce(MappedFile, usize) -> U { if try_map_to(&stream, size) { // Sized + if cfg!(feature="unsafe-mappings") { + // Ensure the output file is open for writing + // XXX: This is not safe, we can't tell if it's a new file being created or if it's appending. I dunno how to find out, lseek doesn't say. + if let Err(err) = reopen_rw(&stream) { + // If this fails, we cannot map it. + if cfg!(debug_assertions) { + eprintln!("Warning: Failed to re-open stdout: {}", err); + } + return Err(stream); + } + } + + // Then map read+write match MappedFile::try_new(stream, size, Perm::ReadWrite, Flags::Shared) { Ok(map) => Ok(trans(map, size)), Err(e) => Err(e.into_inner()), @@ -432,7 +448,16 @@ impl fmt::Debug for ProcessError .finish_non_exhaustive() } } -impl error::Error for ProcessError{} +impl error::Error for ProcessError +{ + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(match self.kind { + ProcessErrorKind::IO(ref io) => io, + _ => return None + }) + } +} impl fmt::Display for ProcessError { #[inline] @@ -456,10 +481,58 @@ impl From for ProcessError } } +impl ProcessError +{ + #[inline] + pub fn context_mut(&mut self) -> Option<&mut Dynamic> + { + self.context.as_deref_mut() + } + #[inline] + pub fn into_context(self) -> Option> + { + self.context + } + #[inline] + pub fn context(&self) -> Option<&Dynamic> + { + self.context.as_deref() + } +} +/// Reopen a file-descriptor as read+write. +pub fn reopen_rw(fd: &(impl AsRawFd+?Sized)) -> io::Result<()> +{ + let fd = fd.as_raw_fd(); + let file = fs::OpenOptions::new() + .read(true) + .write(true) + .open(format!("/proc/self/fd/{fd}"))?; + + // Attempt to seek new fd to old's position, this is important + if unsafe { + let size = libc::lseek(fd, 0, libc::SEEK_CUR); + + libc::lseek(file.as_raw_fd(), match size { + -1 => return Err(io::Error::last_os_error()), + v => v, + }, libc::SEEK_SET) + } < 0 { + return Err(io::Error::last_os_error()); + } + + // File descriptor set up to track accurately + unsafe { + let res = libc::dup2(file.as_raw_fd(), fd); + if res < 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) + } +} /// Create an optimised call table for the cryptographic transformation from `from` to `to`. -pub fn try_create_process(from: T, to: U) -> Result, ProcessError> +pub fn try_create_process(from: T, to: U) -> Result, ProcessError> { let (input, buffsz) = match sized_then_or(from, |input, input_size| { (match MappedFile::try_new(input, input_size, Perm::Readonly, Flags::Private) { @@ -493,9 +566,17 @@ pub fn try_create_process(from: T, to: U) -> Result) -> io::Result> +#[inline] +//TODO: Add metrics, status, progress, diagnostics, etc. reporting +pub fn try_process(mode: impl BorrowMut) -> Result { - todo!("use `try_create_process()`") //XXX: <- next thing to do is integrate the above function into the caller for this (the old impl) one + let sin = io::stdin().lock(); + let sout = io::stdout().lock(); + let proc = try_create_process(sin, sout)?; + if cfg!(debug_assertions) { + eprintln!("Process is: {:?}", proc); + } + Ok(proc.execute(mode)?) } #[cfg(feature="try_process-old")]