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