tempfile_fast/
persistable.rs1use 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
16pub enum PersistableTempFile {
18 Linux(fs::File),
19 Fallback(tempfile::NamedTempFile),
20}
21
22use self::PersistableTempFile::*;
23
24impl PersistableTempFile {
25 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#[derive(Debug)]
149pub struct PersistError {
150 pub error: io::Error,
152 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 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 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 dest_tmp.pop();
226
227 for _ in 0..32768 {
228 dest_tmp.push(format!(".{:x}.tmp", rng.next_u64()));
230
231 match linux::link_at(&file, &dest_tmp) {
232 Ok(()) => {
233 return fs::rename(&dest_tmp, dest).map_err(|error| {
236 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}