diff options
Diffstat (limited to 'vendor/tempfile/src/spooled.rs')
| -rw-r--r-- | vendor/tempfile/src/spooled.rs | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/vendor/tempfile/src/spooled.rs b/vendor/tempfile/src/spooled.rs new file mode 100644 index 00000000..57ba9bea --- /dev/null +++ b/vendor/tempfile/src/spooled.rs @@ -0,0 +1,249 @@ +use crate::file::tempfile; +use crate::tempfile_in; +use std::fs::File; +use std::io::{self, Cursor, Read, Seek, SeekFrom, Write}; +use std::path::{Path, PathBuf}; + +/// A wrapper for the two states of a [`SpooledTempFile`]. Either: +/// +/// 1. An in-memory [`Cursor`] representing the state of the file. +/// 2. A temporary [`File`]. +#[derive(Debug)] +pub enum SpooledData { + InMemory(Cursor<Vec<u8>>), + OnDisk(File), +} + +/// An object that behaves like a regular temporary file, but keeps data in +/// memory until it reaches a configured size, at which point the data is +/// written to a temporary file on disk, and further operations use the file +/// on disk. +#[derive(Debug)] +pub struct SpooledTempFile { + max_size: usize, + dir: Option<PathBuf>, + inner: SpooledData, +} + +/// Create a new [`SpooledTempFile`]. Also see [`spooled_tempfile_in`]. +/// +/// # Security +/// +/// This variant is secure/reliable in the presence of a pathological temporary +/// file cleaner. +/// +/// # Backing Storage +/// +/// By default, the underlying temporary file will be created in your operating system's temporary +/// file directory which is _often_ an in-memory filesystem. You may want to consider using +/// [`spooled_tempfile_in`] instead, passing a storage-backed filesystem (e.g., `/var/tmp` on +/// Linux). +/// +/// # Resource Leaking +/// +/// The temporary file will be automatically removed by the OS when the last +/// handle to it is closed. This doesn't rely on Rust destructors being run, so +/// will (almost) never fail to clean up the temporary file. +/// +/// # Examples +/// +/// ``` +/// use tempfile::spooled_tempfile; +/// use std::io::Write; +/// +/// let mut file = spooled_tempfile(15); +/// +/// writeln!(file, "short line")?; +/// assert!(!file.is_rolled()); +/// +/// // as a result of this write call, the size of the data will exceed +/// // `max_size` (15), so it will be written to a temporary file on disk, +/// // and the in-memory buffer will be dropped +/// writeln!(file, "marvin gardens")?; +/// assert!(file.is_rolled()); +/// # Ok::<(), std::io::Error>(()) +/// ``` +#[inline] +pub fn spooled_tempfile(max_size: usize) -> SpooledTempFile { + SpooledTempFile::new(max_size) +} + +/// Construct a new [`SpooledTempFile`], backed by a file in the specified directory. Use this when, +/// e.g., you need the temporary file to be backed by a specific filesystem (e.g., when your default +/// temporary directory is in-memory). Also see [`spooled_tempfile`]. +/// +/// **NOTE:** The specified path isn't checked until the temporary file is "rolled over" into a real +/// temporary file. If the specified directory isn't writable, writes to the temporary file will +/// fail once the `max_size` is reached. +#[inline] +pub fn spooled_tempfile_in<P: AsRef<Path>>(max_size: usize, dir: P) -> SpooledTempFile { + SpooledTempFile::new_in(max_size, dir) +} + +/// Write a cursor into a temporary file, returning the temporary file. +fn cursor_to_tempfile(cursor: &Cursor<Vec<u8>>, p: &Option<PathBuf>) -> io::Result<File> { + let mut file = match p { + Some(p) => tempfile_in(p)?, + None => tempfile()?, + }; + file.write_all(cursor.get_ref())?; + file.seek(SeekFrom::Start(cursor.position()))?; + Ok(file) +} + +impl SpooledTempFile { + /// Construct a new [`SpooledTempFile`]. + #[must_use] + pub fn new(max_size: usize) -> SpooledTempFile { + SpooledTempFile { + max_size, + dir: None, + inner: SpooledData::InMemory(Cursor::new(Vec::new())), + } + } + + /// Construct a new [`SpooledTempFile`], backed by a file in the specified directory. + #[must_use] + pub fn new_in<P: AsRef<Path>>(max_size: usize, dir: P) -> SpooledTempFile { + SpooledTempFile { + max_size, + dir: Some(dir.as_ref().to_owned()), + inner: SpooledData::InMemory(Cursor::new(Vec::new())), + } + } + + /// Returns true if the file has been rolled over to disk. + #[must_use] + pub fn is_rolled(&self) -> bool { + match self.inner { + SpooledData::InMemory(_) => false, + SpooledData::OnDisk(_) => true, + } + } + + /// Rolls over to a file on disk, regardless of current size. Does nothing + /// if already rolled over. + pub fn roll(&mut self) -> io::Result<()> { + if let SpooledData::InMemory(cursor) = &mut self.inner { + self.inner = SpooledData::OnDisk(cursor_to_tempfile(cursor, &self.dir)?); + } + Ok(()) + } + + /// Truncate the file to the specified size. + pub fn set_len(&mut self, size: u64) -> Result<(), io::Error> { + if size > self.max_size as u64 { + self.roll()?; // does nothing if already rolled over + } + match &mut self.inner { + SpooledData::InMemory(cursor) => { + cursor.get_mut().resize(size as usize, 0); + Ok(()) + } + SpooledData::OnDisk(file) => file.set_len(size), + } + } + + /// Consumes and returns the inner `SpooledData` type. + #[must_use] + pub fn into_inner(self) -> SpooledData { + self.inner + } + + /// Convert into a regular unnamed temporary file, writing it to disk if necessary. + pub fn into_file(self) -> io::Result<File> { + match self.inner { + SpooledData::InMemory(cursor) => cursor_to_tempfile(&cursor, &self.dir), + SpooledData::OnDisk(file) => Ok(file), + } + } +} + +impl Read for SpooledTempFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + match &mut self.inner { + SpooledData::InMemory(cursor) => cursor.read(buf), + SpooledData::OnDisk(file) => file.read(buf), + } + } + + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> { + match &mut self.inner { + SpooledData::InMemory(cursor) => cursor.read_vectored(bufs), + SpooledData::OnDisk(file) => file.read_vectored(bufs), + } + } + + fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> { + match &mut self.inner { + SpooledData::InMemory(cursor) => cursor.read_to_end(buf), + SpooledData::OnDisk(file) => file.read_to_end(buf), + } + } + + fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> { + match &mut self.inner { + SpooledData::InMemory(cursor) => cursor.read_to_string(buf), + SpooledData::OnDisk(file) => file.read_to_string(buf), + } + } + + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + match &mut self.inner { + SpooledData::InMemory(cursor) => cursor.read_exact(buf), + SpooledData::OnDisk(file) => file.read_exact(buf), + } + } +} + +impl Write for SpooledTempFile { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + // roll over to file if necessary + if matches! { + &self.inner, SpooledData::InMemory(cursor) + if cursor.position().saturating_add(buf.len() as u64) > self.max_size as u64 + } { + self.roll()?; + } + + // write the bytes + match &mut self.inner { + SpooledData::InMemory(cursor) => cursor.write(buf), + SpooledData::OnDisk(file) => file.write(buf), + } + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> { + if matches! { + &self.inner, SpooledData::InMemory(cursor) + // Borrowed from the rust standard library. + if bufs + .iter() + .fold(cursor.position(), |a, b| a.saturating_add(b.len() as u64)) + > self.max_size as u64 + } { + self.roll()?; + } + match &mut self.inner { + SpooledData::InMemory(cursor) => cursor.write_vectored(bufs), + SpooledData::OnDisk(file) => file.write_vectored(bufs), + } + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + match &mut self.inner { + SpooledData::InMemory(cursor) => cursor.flush(), + SpooledData::OnDisk(file) => file.flush(), + } + } +} + +impl Seek for SpooledTempFile { + fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> { + match &mut self.inner { + SpooledData::InMemory(cursor) => cursor.seek(pos), + SpooledData::OnDisk(file) => file.seek(pos), + } + } +} |
