/rust/registry/src/index.crates.io-6f17d22bba15001f/tempfile-3.20.0/src/spooled.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use crate::file::tempfile; |
2 | | use crate::tempfile_in; |
3 | | use std::fs::File; |
4 | | use std::io::{self, Cursor, Read, Seek, SeekFrom, Write}; |
5 | | use std::path::{Path, PathBuf}; |
6 | | |
7 | | /// A wrapper for the two states of a [`SpooledTempFile`]. Either: |
8 | | /// |
9 | | /// 1. An in-memory [`Cursor`] representing the state of the file. |
10 | | /// 2. A temporary [`File`]. |
11 | | #[derive(Debug)] |
12 | | pub enum SpooledData { |
13 | | InMemory(Cursor<Vec<u8>>), |
14 | | OnDisk(File), |
15 | | } |
16 | | |
17 | | /// An object that behaves like a regular temporary file, but keeps data in |
18 | | /// memory until it reaches a configured size, at which point the data is |
19 | | /// written to a temporary file on disk, and further operations use the file |
20 | | /// on disk. |
21 | | #[derive(Debug)] |
22 | | pub struct SpooledTempFile { |
23 | | max_size: usize, |
24 | | dir: Option<PathBuf>, |
25 | | inner: SpooledData, |
26 | | } |
27 | | |
28 | | /// Create a new [`SpooledTempFile`]. Also see [`spooled_tempfile_in`]. |
29 | | /// |
30 | | /// # Security |
31 | | /// |
32 | | /// This variant is secure/reliable in the presence of a pathological temporary |
33 | | /// file cleaner. |
34 | | /// |
35 | | /// # Backing Storage |
36 | | /// |
37 | | /// By default, the underlying temporary file will be created in your operating system's temporary |
38 | | /// file directory which is _often_ an in-memory filesystem. You may want to consider using |
39 | | /// [`spooled_tempfile_in`] instead, passing a storage-backed filesystem (e.g., `/var/tmp` on |
40 | | /// Linux). |
41 | | /// |
42 | | /// # Resource Leaking |
43 | | /// |
44 | | /// The temporary file will be automatically removed by the OS when the last |
45 | | /// handle to it is closed. This doesn't rely on Rust destructors being run, so |
46 | | /// will (almost) never fail to clean up the temporary file. |
47 | | /// |
48 | | /// # Examples |
49 | | /// |
50 | | /// ``` |
51 | | /// use tempfile::spooled_tempfile; |
52 | | /// use std::io::Write; |
53 | | /// |
54 | | /// let mut file = spooled_tempfile(15); |
55 | | /// |
56 | | /// writeln!(file, "short line")?; |
57 | | /// assert!(!file.is_rolled()); |
58 | | /// |
59 | | /// // as a result of this write call, the size of the data will exceed |
60 | | /// // `max_size` (15), so it will be written to a temporary file on disk, |
61 | | /// // and the in-memory buffer will be dropped |
62 | | /// writeln!(file, "marvin gardens")?; |
63 | | /// assert!(file.is_rolled()); |
64 | | /// # Ok::<(), std::io::Error>(()) |
65 | | /// ``` |
66 | | #[inline] |
67 | 0 | pub fn spooled_tempfile(max_size: usize) -> SpooledTempFile { |
68 | 0 | SpooledTempFile::new(max_size) |
69 | 0 | } |
70 | | |
71 | | /// Construct a new [`SpooledTempFile`], backed by a file in the specified directory. Use this when, |
72 | | /// e.g., you need the temporary file to be backed by a specific filesystem (e.g., when your default |
73 | | /// temporary directory is in-memory). Also see [`spooled_tempfile`]. |
74 | | /// |
75 | | /// **NOTE:** The specified path isn't checked until the temporary file is "rolled over" into a real |
76 | | /// temporary file. If the specified directory isn't writable, writes to the temporary file will |
77 | | /// fail once the `max_size` is reached. |
78 | | #[inline] |
79 | 0 | pub fn spooled_tempfile_in<P: AsRef<Path>>(max_size: usize, dir: P) -> SpooledTempFile { |
80 | 0 | SpooledTempFile::new_in(max_size, dir) |
81 | 0 | } |
82 | | |
83 | | /// Write a cursor into a temporary file, returning the temporary file. |
84 | 0 | fn cursor_to_tempfile(cursor: &Cursor<Vec<u8>>, p: &Option<PathBuf>) -> io::Result<File> { |
85 | 0 | let mut file = match p { |
86 | 0 | Some(p) => tempfile_in(p)?, |
87 | 0 | None => tempfile()?, |
88 | | }; |
89 | 0 | file.write_all(cursor.get_ref())?; |
90 | 0 | file.seek(SeekFrom::Start(cursor.position()))?; |
91 | 0 | Ok(file) |
92 | 0 | } |
93 | | |
94 | | impl SpooledTempFile { |
95 | | /// Construct a new [`SpooledTempFile`]. |
96 | | #[must_use] |
97 | 0 | pub fn new(max_size: usize) -> SpooledTempFile { |
98 | 0 | SpooledTempFile { |
99 | 0 | max_size, |
100 | 0 | dir: None, |
101 | 0 | inner: SpooledData::InMemory(Cursor::new(Vec::new())), |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | /// Construct a new [`SpooledTempFile`], backed by a file in the specified directory. |
106 | | #[must_use] |
107 | 0 | pub fn new_in<P: AsRef<Path>>(max_size: usize, dir: P) -> SpooledTempFile { |
108 | 0 | SpooledTempFile { |
109 | 0 | max_size, |
110 | 0 | dir: Some(dir.as_ref().to_owned()), |
111 | 0 | inner: SpooledData::InMemory(Cursor::new(Vec::new())), |
112 | 0 | } |
113 | 0 | } |
114 | | |
115 | | /// Returns true if the file has been rolled over to disk. |
116 | | #[must_use] |
117 | 0 | pub fn is_rolled(&self) -> bool { |
118 | 0 | match self.inner { |
119 | 0 | SpooledData::InMemory(_) => false, |
120 | 0 | SpooledData::OnDisk(_) => true, |
121 | | } |
122 | 0 | } |
123 | | |
124 | | /// Rolls over to a file on disk, regardless of current size. Does nothing |
125 | | /// if already rolled over. |
126 | 0 | pub fn roll(&mut self) -> io::Result<()> { |
127 | 0 | if let SpooledData::InMemory(cursor) = &mut self.inner { |
128 | 0 | self.inner = SpooledData::OnDisk(cursor_to_tempfile(cursor, &self.dir)?); |
129 | 0 | } |
130 | 0 | Ok(()) |
131 | 0 | } |
132 | | |
133 | | /// Truncate the file to the specified size. |
134 | 0 | pub fn set_len(&mut self, size: u64) -> Result<(), io::Error> { |
135 | 0 | if size > self.max_size as u64 { |
136 | 0 | self.roll()?; // does nothing if already rolled over |
137 | 0 | } |
138 | 0 | match &mut self.inner { |
139 | 0 | SpooledData::InMemory(cursor) => { |
140 | 0 | cursor.get_mut().resize(size as usize, 0); |
141 | 0 | Ok(()) |
142 | | } |
143 | 0 | SpooledData::OnDisk(file) => file.set_len(size), |
144 | | } |
145 | 0 | } |
146 | | |
147 | | /// Consumes and returns the inner `SpooledData` type. |
148 | | #[must_use] |
149 | 0 | pub fn into_inner(self) -> SpooledData { |
150 | 0 | self.inner |
151 | 0 | } |
152 | | |
153 | | /// Convert into a regular unnamed temporary file, writing it to disk if necessary. |
154 | 0 | pub fn into_file(self) -> io::Result<File> { |
155 | 0 | match self.inner { |
156 | 0 | SpooledData::InMemory(cursor) => cursor_to_tempfile(&cursor, &self.dir), |
157 | 0 | SpooledData::OnDisk(file) => Ok(file), |
158 | | } |
159 | 0 | } |
160 | | } |
161 | | |
162 | | impl Read for SpooledTempFile { |
163 | 0 | fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
164 | 0 | match &mut self.inner { |
165 | 0 | SpooledData::InMemory(cursor) => cursor.read(buf), |
166 | 0 | SpooledData::OnDisk(file) => file.read(buf), |
167 | | } |
168 | 0 | } |
169 | | |
170 | 0 | fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> { |
171 | 0 | match &mut self.inner { |
172 | 0 | SpooledData::InMemory(cursor) => cursor.read_vectored(bufs), |
173 | 0 | SpooledData::OnDisk(file) => file.read_vectored(bufs), |
174 | | } |
175 | 0 | } |
176 | | |
177 | 0 | fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> { |
178 | 0 | match &mut self.inner { |
179 | 0 | SpooledData::InMemory(cursor) => cursor.read_to_end(buf), |
180 | 0 | SpooledData::OnDisk(file) => file.read_to_end(buf), |
181 | | } |
182 | 0 | } |
183 | | |
184 | 0 | fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> { |
185 | 0 | match &mut self.inner { |
186 | 0 | SpooledData::InMemory(cursor) => cursor.read_to_string(buf), |
187 | 0 | SpooledData::OnDisk(file) => file.read_to_string(buf), |
188 | | } |
189 | 0 | } |
190 | | |
191 | 0 | fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { |
192 | 0 | match &mut self.inner { |
193 | 0 | SpooledData::InMemory(cursor) => cursor.read_exact(buf), |
194 | 0 | SpooledData::OnDisk(file) => file.read_exact(buf), |
195 | | } |
196 | 0 | } |
197 | | } |
198 | | |
199 | | impl Write for SpooledTempFile { |
200 | 0 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
201 | | // roll over to file if necessary |
202 | 0 | if matches! { |
203 | 0 | &self.inner, SpooledData::InMemory(cursor) |
204 | 0 | if cursor.position().saturating_add(buf.len() as u64) > self.max_size as u64 |
205 | | } { |
206 | 0 | self.roll()?; |
207 | 0 | } |
208 | | |
209 | | // write the bytes |
210 | 0 | match &mut self.inner { |
211 | 0 | SpooledData::InMemory(cursor) => cursor.write(buf), |
212 | 0 | SpooledData::OnDisk(file) => file.write(buf), |
213 | | } |
214 | 0 | } |
215 | | |
216 | 0 | fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> { |
217 | 0 | if matches! { |
218 | 0 | &self.inner, SpooledData::InMemory(cursor) |
219 | 0 | // Borrowed from the rust standard library. |
220 | 0 | if bufs |
221 | 0 | .iter() |
222 | 0 | .fold(cursor.position(), |a, b| a.saturating_add(b.len() as u64)) |
223 | 0 | > self.max_size as u64 |
224 | | } { |
225 | 0 | self.roll()?; |
226 | 0 | } |
227 | 0 | match &mut self.inner { |
228 | 0 | SpooledData::InMemory(cursor) => cursor.write_vectored(bufs), |
229 | 0 | SpooledData::OnDisk(file) => file.write_vectored(bufs), |
230 | | } |
231 | 0 | } |
232 | | |
233 | | #[inline] |
234 | 0 | fn flush(&mut self) -> io::Result<()> { |
235 | 0 | match &mut self.inner { |
236 | 0 | SpooledData::InMemory(cursor) => cursor.flush(), |
237 | 0 | SpooledData::OnDisk(file) => file.flush(), |
238 | | } |
239 | 0 | } |
240 | | } |
241 | | |
242 | | impl Seek for SpooledTempFile { |
243 | 0 | fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> { |
244 | 0 | match &mut self.inner { |
245 | 0 | SpooledData::InMemory(cursor) => cursor.seek(pos), |
246 | 0 | SpooledData::OnDisk(file) => file.seek(pos), |
247 | | } |
248 | 0 | } |
249 | | } |