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