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