Skip to main content

fs_err/
file.rs

1use std::fs;
2use std::io::{self, Read, Seek, Write};
3use std::path::{Path, PathBuf};
4
5// For file time methods added in Rust 1.75.
6#[cfg(rustc_1_75)]
7use std::{fs::FileTimes, time::SystemTime};
8
9use crate::errors::{Error, ErrorKind};
10use crate::OpenOptions;
11
12#[allow(unused_imports)]
13use crate as fs_err; // for docs
14
15/// Wrapper around [`std::fs::File`] which adds more helpful information to all
16/// errors.
17#[derive(Debug)]
18pub struct File {
19    file: fs::File,
20    path: PathBuf,
21}
22
23/// Opens a [`std::fs::File`] and returns it or an error generator which only
24/// needs the path to produce the error.
25/// Exists for the `crate::read*` functions so they don't unconditionally build
26/// a [`PathBuf`].
27pub(crate) fn open(path: &Path) -> Result<std::fs::File, impl FnOnce(PathBuf) -> io::Error> {
28    fs::File::open(path).map_err(|err| |path| Error::build(err, ErrorKind::OpenFile, path))
29}
30
31/// like [`open()`] but for [`crate::write`].
32pub(crate) fn create(path: &Path) -> Result<std::fs::File, impl FnOnce(PathBuf) -> io::Error> {
33    fs::File::create(path).map_err(|err| |path| Error::build(err, ErrorKind::CreateFile, path))
34}
35
36/// Wrappers for methods from [`std::fs::File`].
37impl File {
38    /// Attempts to open a file in read-only mode.
39    ///
40    /// Wrapper for [`std::fs::File::open`].
41    pub fn open<P>(path: P) -> Result<Self, io::Error>
42    where
43        P: Into<PathBuf>,
44    {
45        let path = path.into();
46        match open(&path) {
47            Ok(file) => Ok(File::from_parts(file, path)),
48            Err(err_gen) => Err(err_gen(path)),
49        }
50    }
51
52    /// Opens a file in write-only mode.
53    ///
54    /// Wrapper for [`std::fs::File::create`].
55    pub fn create<P>(path: P) -> Result<Self, io::Error>
56    where
57        P: Into<PathBuf>,
58    {
59        let path = path.into();
60        match create(&path) {
61            Ok(file) => Ok(File::from_parts(file, path)),
62            Err(err_gen) => Err(err_gen(path)),
63        }
64    }
65
66    /// Opens a file in read-write mode.
67    ///
68    /// Wrapper for [`std::fs::File::create_new`].
69    pub fn create_new<P>(path: P) -> Result<Self, io::Error>
70    where
71        P: Into<PathBuf>,
72    {
73        let path = path.into();
74        // TODO: Use fs::File::create_new once MSRV is at least 1.77
75        match fs::OpenOptions::new()
76            .read(true)
77            .write(true)
78            .create_new(true)
79            .open(&path)
80        {
81            Ok(file) => Ok(File::from_parts(file, path)),
82            Err(err) => Err(Error::build(err, ErrorKind::CreateFile, path)),
83        }
84    }
85
86    /// Returns a new `OpenOptions` object.
87    ///
88    /// Wrapper for [`std::fs::File::options`].
89    pub fn options() -> OpenOptions {
90        OpenOptions::new()
91    }
92
93    /// Attempts to sync all OS-internal metadata to disk.
94    ///
95    /// Wrapper for [`std::fs::File::sync_all`].
96    pub fn sync_all(&self) -> Result<(), io::Error> {
97        self.file
98            .sync_all()
99            .map_err(|source| self.error(source, ErrorKind::SyncFile))
100    }
101
102    /// This function is similar to [`sync_all`], except that it might not
103    /// synchronize file metadata to the filesystem.
104    ///
105    /// Wrapper for [`std::fs::File::sync_data`].
106    ///
107    /// [`sync_all`]: File::sync_all
108    pub fn sync_data(&self) -> Result<(), io::Error> {
109        self.file
110            .sync_data()
111            .map_err(|source| self.error(source, ErrorKind::SyncFile))
112    }
113
114    /// Truncates or extends the underlying file, updating the size of this file to become `size`.
115    ///
116    /// Wrapper for [`std::fs::File::set_len`].
117    pub fn set_len(&self, size: u64) -> Result<(), io::Error> {
118        self.file
119            .set_len(size)
120            .map_err(|source| self.error(source, ErrorKind::SetLen))
121    }
122
123    /// Queries metadata about the underlying file.
124    ///
125    /// Wrapper for [`std::fs::File::metadata`].
126    pub fn metadata(&self) -> Result<fs::Metadata, io::Error> {
127        self.file
128            .metadata()
129            .map_err(|source| self.error(source, ErrorKind::Metadata))
130    }
131
132    /// Creates a new `File` instance that shares the same underlying file handle as the
133    /// existing `File` instance. Reads, writes, and seeks will affect both `File`
134    /// instances simultaneously.
135    ///
136    /// Wrapper for [`std::fs::File::try_clone`].
137    pub fn try_clone(&self) -> Result<Self, io::Error> {
138        self.file
139            .try_clone()
140            .map(|file| File {
141                file,
142                path: self.path.clone(),
143            })
144            .map_err(|source| self.error(source, ErrorKind::Clone))
145    }
146
147    /// Changes the permissions on the underlying file.
148    ///
149    /// Wrapper for [`std::fs::File::set_permissions`].
150    pub fn set_permissions(&self, perm: fs::Permissions) -> Result<(), io::Error> {
151        self.file
152            .set_permissions(perm)
153            .map_err(|source| self.error(source, ErrorKind::SetPermissions))
154    }
155}
156
157/// File time methods added in Rust 1.75.
158#[cfg(rustc_1_75)]
159impl File {
160    /// Changes the timestamps of the underlying file.
161    ///
162    /// Wrapper for [`std::fs::File::set_times`].
163    pub fn set_times(&self, times: FileTimes) -> Result<(), io::Error> {
164        self.file
165            .set_times(times)
166            .map_err(|source| self.error(source, ErrorKind::SetTimes))
167    }
168
169    /// Changes the modification time of the underlying file.
170    ///
171    /// Wrapper for [`std::fs::File::set_modified`].
172    pub fn set_modified(&self, time: SystemTime) -> Result<(), io::Error> {
173        self.file
174            .set_modified(time)
175            .map_err(|source| self.error(source, ErrorKind::SetModified))
176    }
177}
178
179/// Locking methods added in Rust 1.89.
180#[cfg(rustc_1_89)]
181impl File {
182    /// Acquire an exclusive lock on the file. Blocks until the lock can be acquired.
183    ///
184    /// Wrapper for [`std::fs::File::lock`].
185    pub fn lock(&self) -> Result<(), io::Error> {
186        self.file
187            .lock()
188            .map_err(|source| self.error(source, ErrorKind::Lock))
189    }
190
191    /// Acquire a shared (non-exclusive) lock on the file. Blocks until the lock can be acquired.
192    ///
193    /// Wrapper for [`std::fs::File::lock_shared`].
194    pub fn lock_shared(&self) -> Result<(), io::Error> {
195        self.file
196            .lock_shared()
197            .map_err(|source| self.error(source, ErrorKind::Lock))
198    }
199
200    /// Try to acquire an exclusive lock on the file.
201    ///
202    /// Wrapper for [`std::fs::File::try_lock`].
203    pub fn try_lock(&self) -> Result<(), fs::TryLockError> {
204        self.file.try_lock()
205    }
206
207    /// Try to acquire a shared (non-exclusive) lock on the file.
208    ///
209    /// Wrapper for [`std::fs::File::try_lock_shared`].
210    pub fn try_lock_shared(&self) -> Result<(), fs::TryLockError> {
211        self.file.try_lock_shared()
212    }
213
214    /// Release all locks on the file.
215    ///
216    /// Wrapper for [`std::fs::File::unlock`].
217    pub fn unlock(&self) -> Result<(), io::Error> {
218        self.file
219            .unlock()
220            .map_err(|source| self.error(source, ErrorKind::Unlock))
221    }
222}
223
224/// Methods added by fs-err that are not available on [`std::fs::File`].
225impl File {
226    /// Creates a [`fs_err::File`] from a file and its path.
227    pub fn from_parts<P>(file: fs::File, path: P) -> Self
228    where
229        P: Into<PathBuf>,
230    {
231        File {
232            file,
233            path: path.into(),
234        }
235    }
236
237    /// Extract the raw file and its path from this [`fs_err::File`].
238    pub fn into_parts(self) -> (fs::File, PathBuf) {
239        (self.file, self.path)
240    }
241
242    /// Consumes this `File` and returns the underlying [`std::fs::File`].
243    pub fn into_file(self) -> fs::File {
244        self.file
245    }
246
247    /// Consumes this `File` and returns the underlying path as a [`PathBuf`].
248    pub fn into_path(self) -> PathBuf {
249        self.path
250    }
251
252    /// Returns a reference to the underlying [`std::fs::File`].
253    pub fn file(&self) -> &fs::File {
254        &self.file
255    }
256
257    /// Returns a mutable reference to the underlying [`std::fs::File`].
258    pub fn file_mut(&mut self) -> &mut fs::File {
259        &mut self.file
260    }
261
262    /// Returns a reference to the path that this file was created with.
263    pub fn path(&self) -> &Path {
264        &self.path
265    }
266
267    /// Wrap the error in information specific to this `File` object.
268    fn error(&self, source: io::Error, kind: ErrorKind) -> io::Error {
269        Error::build(source, kind, &self.path)
270    }
271}
272
273impl Read for File {
274    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
275        self.file
276            .read(buf)
277            .map_err(|source| self.error(source, ErrorKind::Read))
278    }
279
280    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
281        self.file
282            .read_vectored(bufs)
283            .map_err(|source| self.error(source, ErrorKind::Read))
284    }
285}
286
287impl Read for &File {
288    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
289        (&self.file)
290            .read(buf)
291            .map_err(|source| self.error(source, ErrorKind::Read))
292    }
293
294    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
295        (&self.file)
296            .read_vectored(bufs)
297            .map_err(|source| self.error(source, ErrorKind::Read))
298    }
299}
300
301impl From<File> for fs::File {
302    fn from(file: File) -> Self {
303        file.into_file()
304    }
305}
306
307impl Seek for File {
308    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
309        self.file
310            .seek(pos)
311            .map_err(|source| self.error(source, ErrorKind::Seek))
312    }
313}
314
315impl Seek for &File {
316    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
317        (&self.file)
318            .seek(pos)
319            .map_err(|source| self.error(source, ErrorKind::Seek))
320    }
321}
322
323impl Write for File {
324    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
325        self.file
326            .write(buf)
327            .map_err(|source| self.error(source, ErrorKind::Write))
328    }
329
330    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
331        self.file
332            .write_vectored(bufs)
333            .map_err(|source| self.error(source, ErrorKind::Write))
334    }
335
336    fn flush(&mut self) -> std::io::Result<()> {
337        self.file
338            .flush()
339            .map_err(|source| self.error(source, ErrorKind::Flush))
340    }
341}
342
343impl Write for &File {
344    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
345        (&self.file)
346            .write(buf)
347            .map_err(|source| self.error(source, ErrorKind::Write))
348    }
349
350    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
351        (&self.file)
352            .write_vectored(bufs)
353            .map_err(|source| self.error(source, ErrorKind::Write))
354    }
355
356    fn flush(&mut self) -> std::io::Result<()> {
357        (&self.file)
358            .flush()
359            .map_err(|source| self.error(source, ErrorKind::Flush))
360    }
361}
362
363#[cfg(unix)]
364mod unix {
365    use crate::os::unix::fs::FileExt;
366    use crate::ErrorKind;
367    use std::io;
368    use std::os::unix::fs::FileExt as _;
369    use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
370
371    impl AsRawFd for crate::File {
372        fn as_raw_fd(&self) -> RawFd {
373            self.file().as_raw_fd()
374        }
375    }
376
377    impl IntoRawFd for crate::File {
378        fn into_raw_fd(self) -> RawFd {
379            self.file.into_raw_fd()
380        }
381    }
382
383    impl FileExt for crate::File {
384        fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
385            self.file()
386                .read_at(buf, offset)
387                .map_err(|err| self.error(err, ErrorKind::ReadAt))
388        }
389        fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> {
390            self.file()
391                .read_exact_at(buf, offset)
392                .map_err(|err| self.error(err, ErrorKind::ReadExactAt))
393        }
394        fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
395            self.file()
396                .write_at(buf, offset)
397                .map_err(|err| self.error(err, ErrorKind::WriteAt))
398        }
399        fn write_all_at(&self, buf: &[u8], offset: u64) -> io::Result<()> {
400            self.file()
401                .write_all_at(buf, offset)
402                .map_err(|err| self.error(err, ErrorKind::WriteAllAt))
403        }
404    }
405
406    #[cfg(rustc_1_63)]
407    mod io_safety {
408        use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd};
409
410        impl AsFd for crate::File {
411            fn as_fd(&self) -> BorrowedFd<'_> {
412                self.file().as_fd()
413            }
414        }
415
416        impl From<crate::File> for OwnedFd {
417            fn from(file: crate::File) -> Self {
418                file.into_file().into()
419            }
420        }
421    }
422}
423
424#[cfg(windows)]
425mod windows {
426    use crate::os::windows::fs::FileExt;
427    use crate::ErrorKind;
428    use std::io;
429    use std::os::windows::{
430        fs::FileExt as _,
431        io::{AsRawHandle, IntoRawHandle, RawHandle},
432    };
433
434    impl FileExt for crate::File {
435        fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
436            self.file()
437                .seek_read(buf, offset)
438                .map_err(|err| self.error(err, ErrorKind::SeekRead))
439        }
440
441        fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
442            self.file()
443                .seek_write(buf, offset)
444                .map_err(|err| self.error(err, ErrorKind::SeekWrite))
445        }
446    }
447
448    impl AsRawHandle for crate::File {
449        fn as_raw_handle(&self) -> RawHandle {
450            self.file().as_raw_handle()
451        }
452    }
453
454    // can't be implemented, because the trait doesn't give us a Path
455    // impl std::os::windows::io::FromRawHandle for crate::File {
456    // }
457
458    impl IntoRawHandle for crate::File {
459        fn into_raw_handle(self) -> RawHandle {
460            self.file.into_raw_handle()
461        }
462    }
463
464    #[cfg(rustc_1_63)]
465    mod io_safety {
466        use std::os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle};
467
468        impl AsHandle for crate::File {
469            fn as_handle(&self) -> BorrowedHandle<'_> {
470                self.file().as_handle()
471            }
472        }
473
474        impl From<crate::File> for OwnedHandle {
475            fn from(file: crate::File) -> Self {
476                file.into_parts().0.into()
477            }
478        }
479    }
480}