fs_err/
file.rs

1use std::fs;
2use std::io::{self, Read, Seek, Write};
3use std::path::{Path, PathBuf};
4
5use crate::errors::{Error, ErrorKind};
6
7/// Wrapper around [`std::fs::File`][std::fs::File] which adds more helpful
8/// information to all errors.
9///
10/// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
11#[derive(Debug)]
12pub struct File {
13    file: fs::File,
14    path: PathBuf,
15}
16
17// Opens a std File and returns it or an error generator which only needs the path to produce the error.
18// Exists for the `crate::read*` functions so they don't unconditionally build a PathBuf.
19pub(crate) fn open(path: &Path) -> Result<std::fs::File, impl FnOnce(PathBuf) -> io::Error> {
20    fs::File::open(path).map_err(|err| |path| Error::build(err, ErrorKind::OpenFile, path))
21}
22
23// like `open()` but for `crate::write`
24pub(crate) fn create(path: &Path) -> Result<std::fs::File, impl FnOnce(PathBuf) -> io::Error> {
25    fs::File::create(path).map_err(|err| |path| Error::build(err, ErrorKind::CreateFile, path))
26}
27
28/// Wrappers for methods from [`std::fs::File`][std::fs::File].
29///
30/// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
31impl File {
32    /// Attempts to open a file in read-only mode.
33    ///
34    /// Wrapper for [`File::open`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.open).
35    pub fn open<P>(path: P) -> Result<Self, io::Error>
36    where
37        P: Into<PathBuf>,
38    {
39        let path = path.into();
40        match open(&path) {
41            Ok(file) => Ok(File::from_parts(file, path)),
42            Err(err_gen) => Err(err_gen(path)),
43        }
44    }
45
46    /// Opens a file in write-only mode.
47    ///
48    /// Wrapper for [`File::create`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.create).
49    pub fn create<P>(path: P) -> Result<Self, io::Error>
50    where
51        P: Into<PathBuf>,
52    {
53        let path = path.into();
54        match create(&path) {
55            Ok(file) => Ok(File::from_parts(file, path)),
56            Err(err_gen) => Err(err_gen(path)),
57        }
58    }
59
60    /// Wrapper for [`OpenOptions::open`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html#method.open).
61    ///
62    /// This takes [`&std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html),
63    /// not [`crate::OpenOptions`].
64    #[deprecated = "use fs_err::OpenOptions::open instead"]
65    pub fn from_options<P>(path: P, options: &fs::OpenOptions) -> Result<Self, io::Error>
66    where
67        P: Into<PathBuf>,
68    {
69        let path = path.into();
70        match options.open(&path) {
71            Ok(file) => Ok(File::from_parts(file, path)),
72            Err(source) => Err(Error::build(source, ErrorKind::OpenFile, path)),
73        }
74    }
75
76    /// Attempts to sync all OS-internal metadata to disk.
77    ///
78    /// Wrapper for [`File::sync_all`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.sync_all).
79    pub fn sync_all(&self) -> Result<(), io::Error> {
80        self.file
81            .sync_all()
82            .map_err(|source| self.error(source, ErrorKind::SyncFile))
83    }
84
85    /// This function is similar to [`sync_all`], except that it might not synchronize file metadata to the filesystem.
86    ///
87    /// Wrapper for [`File::sync_data`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.sync_data).
88    pub fn sync_data(&self) -> Result<(), io::Error> {
89        self.file
90            .sync_data()
91            .map_err(|source| self.error(source, ErrorKind::SyncFile))
92    }
93
94    /// Truncates or extends the underlying file, updating the size of this file to become `size`.
95    ///
96    /// Wrapper for [`File::set_len`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.set_len).
97    pub fn set_len(&self, size: u64) -> Result<(), io::Error> {
98        self.file
99            .set_len(size)
100            .map_err(|source| self.error(source, ErrorKind::SetLen))
101    }
102
103    /// Queries metadata about the underlying file.
104    ///
105    /// Wrapper for [`File::metadata`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.metadata).
106    pub fn metadata(&self) -> Result<fs::Metadata, io::Error> {
107        self.file
108            .metadata()
109            .map_err(|source| self.error(source, ErrorKind::Metadata))
110    }
111
112    /// Creates a new `File` instance that shares the same underlying file handle as the
113    /// existing `File` instance. Reads, writes, and seeks will affect both `File`
114    /// instances simultaneously.
115    ///
116    /// Wrapper for [`File::try_clone`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.try_clone).
117    pub fn try_clone(&self) -> Result<Self, io::Error> {
118        self.file
119            .try_clone()
120            .map(|file| File {
121                file,
122                path: self.path.clone(),
123            })
124            .map_err(|source| self.error(source, ErrorKind::Clone))
125    }
126
127    /// Changes the permissions on the underlying file.
128    ///
129    /// Wrapper for [`File::set_permissions`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.set_permissions).
130    pub fn set_permissions(&self, perm: fs::Permissions) -> Result<(), io::Error> {
131        self.file
132            .set_permissions(perm)
133            .map_err(|source| self.error(source, ErrorKind::SetPermissions))
134    }
135}
136
137/// Methods added by fs-err that are not available on
138/// [`std::fs::File`][std::fs::File].
139///
140/// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
141impl File {
142    /// Creates a [`File`](struct.File.html) from a raw file and its path.
143    pub fn from_parts<P>(file: fs::File, path: P) -> Self
144    where
145        P: Into<PathBuf>,
146    {
147        File {
148            file,
149            path: path.into(),
150        }
151    }
152
153    /// Extract the raw file and its path from this [`File`](struct.File.html)
154    pub fn into_parts(self) -> (fs::File, PathBuf) {
155        (self.file, self.path)
156    }
157
158    /// Returns a reference to the underlying [`std::fs::File`][std::fs::File].
159    ///
160    /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
161    pub fn file(&self) -> &fs::File {
162        &self.file
163    }
164
165    /// Returns a mutable reference to the underlying [`std::fs::File`][std::fs::File].
166    ///
167    /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
168    pub fn file_mut(&mut self) -> &mut fs::File {
169        &mut self.file
170    }
171
172    /// Returns a reference to the path that this file was created with.
173    pub fn path(&self) -> &Path {
174        &self.path
175    }
176
177    /// Wrap the error in information specific to this `File` object.
178    fn error(&self, source: io::Error, kind: ErrorKind) -> io::Error {
179        Error::build(source, kind, &self.path)
180    }
181}
182
183impl Read for File {
184    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
185        self.file
186            .read(buf)
187            .map_err(|source| self.error(source, ErrorKind::Read))
188    }
189
190    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
191        self.file
192            .read_vectored(bufs)
193            .map_err(|source| self.error(source, ErrorKind::Read))
194    }
195}
196
197impl<'a> Read for &'a File {
198    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
199        (&self.file)
200            .read(buf)
201            .map_err(|source| self.error(source, ErrorKind::Read))
202    }
203
204    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
205        (&self.file)
206            .read_vectored(bufs)
207            .map_err(|source| self.error(source, ErrorKind::Read))
208    }
209}
210
211impl From<File> for fs::File {
212    fn from(file: File) -> Self {
213        file.into_parts().0
214    }
215}
216
217impl Seek for File {
218    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
219        self.file
220            .seek(pos)
221            .map_err(|source| self.error(source, ErrorKind::Seek))
222    }
223}
224
225impl<'a> Seek for &'a File {
226    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
227        (&self.file)
228            .seek(pos)
229            .map_err(|source| self.error(source, ErrorKind::Seek))
230    }
231}
232
233impl Write for File {
234    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
235        self.file
236            .write(buf)
237            .map_err(|source| self.error(source, ErrorKind::Write))
238    }
239
240    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
241        self.file
242            .write_vectored(bufs)
243            .map_err(|source| self.error(source, ErrorKind::Write))
244    }
245
246    fn flush(&mut self) -> std::io::Result<()> {
247        self.file
248            .flush()
249            .map_err(|source| self.error(source, ErrorKind::Flush))
250    }
251}
252
253impl<'a> Write for &'a File {
254    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
255        (&self.file)
256            .write(buf)
257            .map_err(|source| self.error(source, ErrorKind::Write))
258    }
259
260    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
261        (&self.file)
262            .write_vectored(bufs)
263            .map_err(|source| self.error(source, ErrorKind::Write))
264    }
265
266    fn flush(&mut self) -> std::io::Result<()> {
267        (&self.file)
268            .flush()
269            .map_err(|source| self.error(source, ErrorKind::Flush))
270    }
271}
272
273#[cfg(unix)]
274mod unix {
275    use crate::os::unix::fs::FileExt;
276    use crate::ErrorKind;
277    use std::io;
278    use std::os::unix::fs::FileExt as _;
279    use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
280
281    impl AsRawFd for crate::File {
282        fn as_raw_fd(&self) -> RawFd {
283            self.file().as_raw_fd()
284        }
285    }
286
287    impl IntoRawFd for crate::File {
288        fn into_raw_fd(self) -> RawFd {
289            self.file.into_raw_fd()
290        }
291    }
292
293    impl FileExt for crate::File {
294        fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
295            self.file()
296                .read_at(buf, offset)
297                .map_err(|err| self.error(err, ErrorKind::ReadAt))
298        }
299        fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
300            self.file()
301                .write_at(buf, offset)
302                .map_err(|err| self.error(err, ErrorKind::WriteAt))
303        }
304    }
305
306    #[cfg(feature = "io_safety")]
307    mod io_safety {
308        use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd};
309
310        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]
311        impl AsFd for crate::File {
312            fn as_fd(&self) -> BorrowedFd<'_> {
313                self.file().as_fd()
314            }
315        }
316
317        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]
318        impl From<crate::File> for OwnedFd {
319            fn from(file: crate::File) -> Self {
320                file.into_parts().0.into()
321            }
322        }
323    }
324}
325
326#[cfg(windows)]
327mod windows {
328    use crate::os::windows::fs::FileExt;
329    use crate::ErrorKind;
330    use std::io;
331    use std::os::windows::{
332        fs::FileExt as _,
333        io::{AsRawHandle, IntoRawHandle, RawHandle},
334    };
335
336    impl FileExt for crate::File {
337        fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
338            self.file()
339                .seek_read(buf, offset)
340                .map_err(|err| self.error(err, ErrorKind::SeekRead))
341        }
342
343        fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
344            self.file()
345                .seek_write(buf, offset)
346                .map_err(|err| self.error(err, ErrorKind::SeekWrite))
347        }
348    }
349
350    impl AsRawHandle for crate::File {
351        fn as_raw_handle(&self) -> RawHandle {
352            self.file().as_raw_handle()
353        }
354    }
355
356    // can't be implemented, because the trait doesn't give us a Path
357    // impl std::os::windows::io::FromRawHandle for crate::File {
358    // }
359
360    impl IntoRawHandle for crate::File {
361        fn into_raw_handle(self) -> RawHandle {
362            self.file.into_raw_handle()
363        }
364    }
365
366    #[cfg(feature = "io_safety")]
367    mod io_safety {
368        use std::os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle};
369
370        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]
371        impl AsHandle for crate::File {
372            fn as_handle(&self) -> BorrowedHandle<'_> {
373                self.file().as_handle()
374            }
375        }
376
377        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]
378        impl From<crate::File> for OwnedHandle {
379            fn from(file: crate::File) -> Self {
380                file.into_parts().0.into()
381            }
382        }
383    }
384}