tempfile_fast/
persistable.rs

1use std::fmt;
2use std::fs;
3use std::io;
4use std::io::Read;
5use std::io::Seek;
6use std::io::SeekFrom;
7use std::io::Write;
8use std::ops::Deref;
9use std::ops::DerefMut;
10use std::path::Path;
11
12use rand::RngCore;
13
14use crate::linux;
15use tempfile;
16
17/// An abstraction over different platform-specific temporary file optimisations.
18pub enum PersistableTempFile {
19    Linux(fs::File),
20    Fallback(tempfile::NamedTempFile),
21}
22
23use self::PersistableTempFile::*;
24
25impl PersistableTempFile {
26    /// Create a temporary file in a given filesystem, or, if the filesystem
27    /// does not support creating secure temporary files, create a
28    /// [`tempfile::NamedTempFile`].
29    ///
30    /// [`tempfile::NamedTempFile`]: https://docs.rs/tempfile/*/tempfile/struct.NamedTempFile.html
31    pub fn new_in<P: AsRef<Path>>(dir: P) -> io::Result<PersistableTempFile> {
32        if let Ok(file) = linux::create_nonexclusive_tempfile_in(&dir) {
33            return Ok(Linux(file));
34        }
35
36        Ok(Fallback(tempfile::Builder::new().tempfile_in(dir)?))
37    }
38}
39
40impl AsRef<fs::File> for PersistableTempFile {
41    #[inline]
42    fn as_ref(&self) -> &fs::File {
43        match *self {
44            Linux(ref file) => file,
45            Fallback(ref named) => named.as_file(),
46        }
47    }
48}
49
50impl AsMut<fs::File> for PersistableTempFile {
51    #[inline]
52    fn as_mut(&mut self) -> &mut fs::File {
53        match *self {
54            Linux(ref mut file) => file,
55            Fallback(ref mut named) => named.as_file_mut(),
56        }
57    }
58}
59
60impl Deref for PersistableTempFile {
61    type Target = fs::File;
62    #[inline]
63    fn deref(&self) -> &fs::File {
64        self.as_ref()
65    }
66}
67
68impl DerefMut for PersistableTempFile {
69    #[inline]
70    fn deref_mut(&mut self) -> &mut fs::File {
71        self.as_mut()
72    }
73}
74
75impl fmt::Debug for PersistableTempFile {
76    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77        write!(
78            f,
79            "PersistableTempFile::{}",
80            match *self {
81                Linux(_) => "Linux",
82                Fallback(_) => "Fallback",
83            }
84        )
85    }
86}
87
88impl Read for PersistableTempFile {
89    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
90        self.as_mut().read(buf)
91    }
92}
93
94impl Write for PersistableTempFile {
95    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
96        self.as_mut().write(buf)
97    }
98
99    fn flush(&mut self) -> io::Result<()> {
100        self.as_mut().flush()
101    }
102}
103
104impl Seek for PersistableTempFile {
105    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
106        self.as_mut().seek(pos)
107    }
108}
109
110impl<'a> Read for &'a PersistableTempFile {
111    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
112        self.as_ref().read(buf)
113    }
114}
115
116impl<'a> Write for &'a PersistableTempFile {
117    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
118        self.as_ref().write(buf)
119    }
120
121    fn flush(&mut self) -> io::Result<()> {
122        self.as_ref().flush()
123    }
124}
125
126impl<'a> Seek for &'a PersistableTempFile {
127    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
128        self.as_ref().seek(pos)
129    }
130}
131
132#[cfg(unix)]
133impl ::std::os::unix::io::AsRawFd for PersistableTempFile {
134    #[inline]
135    fn as_raw_fd(&self) -> ::std::os::unix::io::RawFd {
136        self.as_ref().as_raw_fd()
137    }
138}
139
140#[cfg(windows)]
141impl ::std::os::windows::io::AsRawHandle for PersistableTempFile {
142    #[inline]
143    fn as_raw_handle(&self) -> ::std::os::windows::io::RawHandle {
144        self.as_ref().as_raw_handle()
145    }
146}
147
148/// Error returned when persisting a temporary file fails.
149#[derive(Debug)]
150pub struct PersistError {
151    /// The underlying IO error.
152    pub error: io::Error,
153    /// The temporary file that couldn't be persisted.
154    pub file: PersistableTempFile,
155}
156
157impl PersistError {
158    fn new(error: io::Error, file: fs::File) -> PersistError {
159        PersistError {
160            error,
161            file: PersistableTempFile::Linux(file),
162        }
163    }
164}
165
166impl From<tempfile::PersistError> for PersistError {
167    fn from(e: tempfile::PersistError) -> Self {
168        PersistError {
169            error: e.error,
170            file: PersistableTempFile::Fallback(e.file),
171        }
172    }
173}
174
175impl PersistableTempFile {
176    /// Store this temporary file into a real file path.
177    ///
178    /// The path must not exist and must be on the same mounted filesystem.
179    ///
180    /// (Note: Linux permits a filesystem to be mounted at multiple points,
181    /// but the `link()` function does not work across different mount points,
182    /// even if the same filesystem is mounted on both.)
183    pub fn persist_noclobber<P: AsRef<Path>>(self, dest: P) -> Result<(), PersistError> {
184        match self {
185            Linux(mut file) => {
186                if let Err(error) = file.flush() {
187                    return Err(PersistError::new(error, file));
188                }
189                linux::link_at(&file, dest).map_err(|error| PersistError::new(error, file))
190            }
191            Fallback(named) => named
192                .persist_noclobber(dest)
193                .map(|_| ())
194                .map_err(PersistError::from),
195        }
196    }
197
198    /// Store this temporary file into a real name.
199    ///
200    /// The path must be on the same mounted filesystem. It may exist, and will be overwritten.
201    ///
202    /// This method may create a named temporary file, and, in pathological failure cases,
203    /// may silently fail to remove this temporary file. Sorry.
204    ///
205    /// (Note: Linux permits a filesystem to be mounted at multiple points,
206    /// but the `link()` function does not work across different mount points,
207    /// even if the same filesystem is mounted on both.)
208    pub fn persist_by_rename<P: AsRef<Path>>(self, dest: P) -> Result<(), PersistError> {
209        let mut file = match self {
210            Linux(file) => file,
211            Fallback(named) => return named.persist(dest).map(|_| ()).map_err(PersistError::from),
212        };
213
214        if let Err(error) = file.flush() {
215            return Err(PersistError::new(error, file));
216        }
217
218        if linux::link_at(&file, &dest).is_ok() {
219            return Ok(());
220        };
221
222        let mut dest_tmp = dest.as_ref().to_path_buf();
223        let mut rng = ::rand::thread_rng();
224
225        // pop the filename off
226        dest_tmp.pop();
227
228        for _ in 0..32768 {
229            // add a new filename
230            dest_tmp.push(format!(".{:x}.tmp", rng.next_u64()));
231
232            match linux::link_at(&file, &dest_tmp) {
233                Ok(()) => {
234                    // we succeeded in converting into a named temporary file,
235                    // now overwrite the destination
236                    return fs::rename(&dest_tmp, dest).map_err(|error| {
237                        // we couldn't overwrite the destination. Try and remove the
238                        // temporary file we created, but, if we can't, just sigh.
239                        let _ = fs::remove_file(&dest_tmp);
240
241                        PersistError::new(error, file)
242                    });
243                }
244                Err(error) => {
245                    if io::ErrorKind::AlreadyExists != error.kind() {
246                        return Err(PersistError::new(error, file));
247                    }
248                }
249            };
250            dest_tmp.pop();
251        }
252
253        Err(PersistError::new(
254            io::Error::new(io::ErrorKind::Other, "couldn't create temporary file"),
255            file,
256        ))
257    }
258}