From 413be3f334e7585579744d44a86edebf77a023ad Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 18 Sep 2020 11:29:09 +0300 Subject: [PATCH] Add read-only memfd backed temporary files --- src/components/mail/compose.rs | 11 +- src/components/mail/pgp.rs | 4 +- src/components/mail/view.rs | 3 +- src/components/mail/view/envelope.rs | 3 +- src/components/mail/view/html.rs | 2 +- src/conf/accounts.rs | 6 +- src/mailcap.rs | 10 +- src/state.rs | 2 +- src/types.rs | 6 +- src/types/files.rs | 198 +++++++++++++++++++++++++++ src/types/helpers.rs | 105 -------------- 11 files changed, 227 insertions(+), 123 deletions(-) create mode 100644 src/types/files.rs delete mode 100644 src/types/helpers.rs diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index f3a94e38c..dad84e31d 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -42,8 +42,8 @@ enum Cursor { #[derive(Debug)] enum EmbedStatus { - Stopped(Arc>, File), - Running(Arc>, File), + Stopped(Arc>, MeliFile), + Running(Arc>, MeliFile), } impl std::ops::Deref for EmbedStatus { @@ -1132,10 +1132,11 @@ impl Component for Composer { }; /* update Draft's headers based on form values */ self.update_draft(); - let f = create_temp_file( + let f = MeliFile::create_temp_file( self.draft.to_string().unwrap().as_str().as_bytes(), None, None, + false, true, ); @@ -1230,11 +1231,11 @@ impl Component for Composer { )); return false; } - let f = create_temp_file(&[], None, None, true); + let f = MeliFile::create_temp_file(&[], None, None, false, true); match std::process::Command::new("sh") .args(&["-c", command]) .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::from(f.file())) + .stdout(std::process::Stdio::from(f.get_file())) .spawn() { Ok(child) => { diff --git a/src/components/mail/pgp.rs b/src/components/mail/pgp.rs index 264c95846..4f5193796 100644 --- a/src/components/mail/pgp.rs +++ b/src/components/mail/pgp.rs @@ -26,8 +26,8 @@ use std::process::{Command, Stdio}; pub fn verify_signature(a: &Attachment, context: &mut Context) -> Vec { match melib::signatures::verify_signature(a) { Ok((bytes, sig)) => { - let bytes_file = create_temp_file(&bytes, None, None, true); - let signature_file = create_temp_file(sig, None, None, true); + let bytes_file = MeliFile::create_temp_file(&bytes, None, None, true, true); + let signature_file = MeliFile::create_temp_file(sig, None, None, true, true); match Command::new( context .settings diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index c7e697405..b87619e3d 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -541,11 +541,12 @@ impl MailView { name_opt = name.as_ref().map(|n| n.clone()); } if let Ok(binary) = binary { - let p = create_temp_file( + let p = MeliFile::create_temp_file( &decode(u, None), name_opt.as_ref().map(String::as_str), None, true, + true, ); match debug!(context.plugin_manager.activate_hook( "attachment-view", diff --git a/src/components/mail/view/envelope.rs b/src/components/mail/view/envelope.rs index 5a18f3aff..ef51350c7 100644 --- a/src/components/mail/view/envelope.rs +++ b/src/components/mail/view/envelope.rs @@ -439,11 +439,12 @@ impl Component for EnvelopeView { let attachment_type = u.mime_type(); let binary = query_default_app(&attachment_type); if let Ok(binary) = binary { - let p = create_temp_file( + let p = MeliFile::create_temp_file( &decode(u, None), name.as_ref().map(String::as_str), None, true, + true, ); match Command::new(&binary) .arg(p.path()) diff --git a/src/components/mail/view/html.rs b/src/components/mail/view/html.rs index 1bc02e12d..0cea061cf 100644 --- a/src/components/mail/view/html.rs +++ b/src/components/mail/view/html.rs @@ -133,7 +133,7 @@ impl Component for HtmlView { if let UIEvent::Input(Key::Char('v')) = event { let binary = query_default_app("text/html"); if let Ok(binary) = binary { - let p = create_temp_file(&self.bytes, None, None, true); + let p = MeliFile::create_temp_file(&self.bytes, None, None, true, true); match Command::new(&binary) .arg(p.path()) .stdin(Stdio::piped()) diff --git a/src/conf/accounts.rs b/src/conf/accounts.rs index 95d642ada..951421a58 100644 --- a/src/conf/accounts.rs +++ b/src/conf/accounts.rs @@ -1133,7 +1133,7 @@ impl Account { if let Some(mailbox_hash) = saved_at { Ok(mailbox_hash) } else { - let file = crate::types::create_temp_file(bytes, None, None, false); + let file = crate::types::MeliFile::create_temp_file(bytes, None, None, false, false); debug!("message saved in {}", file.path.display()); melib::log( format!( @@ -1684,7 +1684,9 @@ impl Account { let r = channel.try_recv().unwrap(); if let Some(Err(err)) = r { melib::log(format!("Could not save message: {}", err), melib::ERROR); - let file = crate::types::create_temp_file(bytes, None, None, false); + let file = crate::types::MeliFile::create_temp_file( + bytes, None, None, false, false, + ); debug!("message saved in {}", file.path.display()); melib::log( format!( diff --git a/src/mailcap.rs b/src/mailcap.rs index 745945d3c..2ace547d5 100644 --- a/src/mailcap.rs +++ b/src/mailcap.rs @@ -22,7 +22,7 @@ /*! Find mailcap entries to execute attachments. */ use crate::state::Context; -use crate::types::{create_temp_file, ForkType, UIEvent}; +use crate::types::{ForkType, MeliFile, UIEvent}; use melib::attachments::decode; use melib::text_processing::GlobMatch; use melib::{email::Attachment, MeliError, Result}; @@ -157,7 +157,13 @@ impl MailcapEntry { .map(|arg| match *arg { "%s" => { needs_stdin = false; - let _f = create_temp_file(&decode(a, None), None, None, true); + let _f = MeliFile::create_temp_file( + &decode(a, None), + None, + None, + true, + true, + ); let p = _f.path().display().to_string(); f = Some(_f); p diff --git a/src/state.rs b/src/state.rs index c3ab65c5a..fa7e81c92 100644 --- a/src/state.rs +++ b/src/state.rs @@ -117,7 +117,7 @@ pub struct Context { pub children: Vec, pub plugin_manager: PluginManager, - pub temp_files: Vec, + pub temp_files: Vec, } impl Context { diff --git a/src/types.rs b/src/types.rs index 1e4e1e580..3a4f69ec0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -32,8 +32,8 @@ */ extern crate serde; #[macro_use] -mod helpers; -pub use self::helpers::*; +mod files; +pub use self::files::*; use super::command::Action; use super::jobs::JobId; @@ -86,7 +86,7 @@ pub enum ForkType { /// Embed pty Embed(Pid), Generic(std::process::Child), - NewDraft(File, std::process::Child), + NewDraft(MeliFile, std::process::Child), } #[derive(Debug, PartialEq, Copy, Clone)] diff --git a/src/types/files.rs b/src/types/files.rs new file mode 100644 index 000000000..0e4749140 --- /dev/null +++ b/src/types/files.rs @@ -0,0 +1,198 @@ +/* + * meli + * + * Copyright 2017-2018 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use std::fs; +use std::fs::OpenOptions; +use std::io::{Read, Write}; +use std::os::unix::fs::PermissionsExt; +use std::path::PathBuf; + +use uuid::Uuid; + +enum FileType { + Real, + #[cfg(target_os = "linux")] + Memory { + fd: std::os::unix::io::RawFd, + }, +} + +impl core::fmt::Debug for FileType { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + FileType::Real => fmt.debug_struct("FileType::Real").finish(), + #[cfg(target_os = "linux")] + FileType::Memory { fd } => fmt + .debug_struct(&format!("FileType::Memory({})", fd)) + .finish(), + } + } +} + +#[derive(Debug)] +pub struct MeliFile { + backing: FileType, + pub path: PathBuf, + delete_on_drop: bool, +} + +impl Drop for MeliFile { + fn drop(&mut self) { + if self.delete_on_drop { + std::fs::remove_file(self.path()).unwrap_or_else(|_| {}); + } + } +} + +impl MeliFile { + pub fn get_file(&self) -> std::fs::File { + OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&self.path) + .unwrap() + } + + pub fn path(&self) -> &PathBuf { + &self.path + } + + pub fn read_to_string(&self) -> String { + let mut buf = Vec::new(); + let mut f = fs::File::open(&self.path) + .unwrap_or_else(|_| panic!("Can't open {}", &self.path.display())); + f.read_to_end(&mut buf) + .unwrap_or_else(|_| panic!("Can't read {}", &self.path.display())); + String::from_utf8(buf).unwrap() + } + + /// Returned [`MeliFile`] will be deleted when dropped if delete_on_drop is set, so make sure to + /// add it on [`Context'] `temp_files` to reap it later. + pub fn create_temp_file( + bytes: &[u8], + filename: Option<&str>, + path: Option<&PathBuf>, + read_only: bool, + delete_on_drop: bool, + ) -> MeliFile { + #[cfg(target_os = "linux")] + if delete_on_drop && read_only && filename.is_none() && path.is_none() { + debug!("creating memfd"); + match MeliFile::create_mem_file(bytes) { + Ok(f) => return f, + Err(err) => { + debug!("creating memfd failed {:?}", &err); + melib::log( + format!( + "Could not memfd_create file of len {}: {}", + bytes.len(), + err + ), + melib::LoggingLevel::DEBUG, + ); + } + } + } + + let mut dir = std::env::temp_dir(); + + let path = path.unwrap_or_else(|| { + dir.push("meli"); + std::fs::DirBuilder::new() + .recursive(true) + .create(&dir) + .unwrap(); + if let Some(filename) = filename { + dir.push(filename) + } else { + let u = Uuid::new_v4(); + dir.push(u.to_hyphenated().to_string()); + } + &dir + }); + + let mut f = std::fs::File::create(path).unwrap(); + let metadata = f.metadata().unwrap(); + let mut permissions = metadata.permissions(); + + permissions.set_mode(0o600); // Read/write for owner only. + f.set_permissions(permissions).unwrap(); + + f.write_all(bytes).unwrap(); + f.flush().unwrap(); + MeliFile { + backing: FileType::Real, + path: path.clone(), + delete_on_drop, + } + } + + #[cfg(target_os = "linux")] + pub fn create_mem_file(bytes: &[u8]) -> melib::Result { + use std::convert::TryInto; + + use nix::fcntl::SealFlag; + use nix::sys::memfd::{memfd_create, MemFdCreateFlag}; + use std::ffi::CStr; + let name: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(&b"meli\0"[..]) }; + let len = bytes + .len() + .try_into() + .map_err(|err| Box::new(err) as Box)?; + + let fd = debug!(memfd_create( + name, + MemFdCreateFlag::MFD_ALLOW_SEALING //| MemFdCreateFlag::MFD_CLOEXEC, + ))?; + debug!(nix::unistd::ftruncate(fd, len))?; + let addr = unsafe { + debug!(nix::sys::mman::mmap( + std::ptr::null_mut(), + bytes.len(), + nix::sys::mman::ProtFlags::PROT_WRITE, + nix::sys::mman::MapFlags::MAP_SHARED, + fd, + 0, + ))? + }; + unsafe { std::ptr::copy_nonoverlapping(bytes.as_ptr(), addr as *mut u8, bytes.len()) }; + debug!(unsafe { nix::sys::mman::munmap(addr, bytes.len()) })?; + debug!(nix::fcntl::fcntl( + fd, + nix::fcntl::FcntlArg::F_ADD_SEALS( + SealFlag::F_SEAL_SHRINK + | SealFlag::F_SEAL_GROW + | SealFlag::F_SEAL_WRITE + | SealFlag::F_SEAL_SEAL, + ), + ))?; + Ok(MeliFile { + backing: FileType::Memory { fd }, + path: PathBuf::from(format!( + "/proc/{pid}/fd/{fd}", + pid = std::process::id(), + fd = fd + )), + delete_on_drop: true, + }) + } +} diff --git a/src/types/helpers.rs b/src/types/helpers.rs deleted file mode 100644 index 8c02896c0..000000000 --- a/src/types/helpers.rs +++ /dev/null @@ -1,105 +0,0 @@ -/* - * meli - * - * Copyright 2017-2018 Manos Pitsidianakis - * - * This file is part of meli. - * - * meli is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * meli is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with meli. If not, see . - */ - -use std::fs; -use std::fs::OpenOptions; -use std::io::{Read, Write}; -use std::os::unix::fs::PermissionsExt; -use std::path::PathBuf; - -use uuid::Uuid; - -#[derive(Debug)] -pub struct File { - pub path: PathBuf, - delete_on_drop: bool, -} - -impl Drop for File { - fn drop(&mut self) { - if self.delete_on_drop { - std::fs::remove_file(self.path()).unwrap_or_else(|_| {}); - } - } -} - -impl File { - pub fn file(&self) -> std::fs::File { - OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(&self.path) - .unwrap() - } - - pub fn path(&self) -> &PathBuf { - &self.path - } - pub fn read_to_string(&self) -> String { - let mut buf = Vec::new(); - let mut f = fs::File::open(&self.path) - .unwrap_or_else(|_| panic!("Can't open {}", &self.path.display())); - f.read_to_end(&mut buf) - .unwrap_or_else(|_| panic!("Can't read {}", &self.path.display())); - String::from_utf8(buf).unwrap() - } -} - -/// Returned `File` will be deleted when dropped if delete_on_drop is set, so make sure to add it on `context.temp_files` -/// to reap it later. -pub fn create_temp_file( - bytes: &[u8], - filename: Option<&str>, - path: Option<&PathBuf>, - delete_on_drop: bool, -) -> File { - let mut dir = std::env::temp_dir(); - - let path = path.unwrap_or_else(|| { - dir.push("meli"); - std::fs::DirBuilder::new() - .recursive(true) - .create(&dir) - .unwrap(); - if let Some(filename) = filename { - dir.push(filename) - } else { - let u = Uuid::new_v4(); - dir.push(u.to_hyphenated().to_string()); - } - &dir - }); - - let mut f = std::fs::File::create(path).unwrap(); - let metadata = f.metadata().unwrap(); - let mut permissions = metadata.permissions(); - - permissions.set_mode(0o600); // Read/write for owner only. - f.set_permissions(permissions).unwrap(); - - f.write_all(bytes).unwrap(); - f.flush().unwrap(); - File { - path: path.clone(), - delete_on_drop, - } -}