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    /// Consumes this [`File`](struct.File.html) and returns the underlying
171    /// [`std::fs::File`][std::fs::File].
172    pub fn into_file(self) -> fs::File {
173        self.file
174    }
175
176    /// Consumes this [`File`](struct.File.html) and returns the underlying
177    /// path as a [`PathBuf`].
178    pub fn into_path(self) -> PathBuf {
179        self.path
180    }
181
182    /// Returns a reference to the underlying [`std::fs::File`][std::fs::File].
183    ///
184    /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
185    pub fn file(&self) -> &fs::File {
186        &self.file
187    }
188
189    /// Returns a mutable reference to the underlying [`std::fs::File`][std::fs::File].
190    ///
191    /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
192    pub fn file_mut(&mut self) -> &mut fs::File {
193        &mut self.file
194    }
195
196    /// Returns a reference to the path that this file was created with.
197    pub fn path(&self) -> &Path {
198        &self.path
199    }
200
201    /// Wrap the error in information specific to this `File` object.
202    fn error(&self, source: io::Error, kind: ErrorKind) -> io::Error {
203        Error::build(source, kind, &self.path)
204    }
205}
206
207impl Read for File {
208    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
209        self.file
210            .read(buf)
211            .map_err(|source| self.error(source, ErrorKind::Read))
212    }
213
214    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
215        self.file
216            .read_vectored(bufs)
217            .map_err(|source| self.error(source, ErrorKind::Read))
218    }
219}
220
221impl Read for &File {
222    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
223        (&self.file)
224            .read(buf)
225            .map_err(|source| self.error(source, ErrorKind::Read))
226    }
227
228    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
229        (&self.file)
230            .read_vectored(bufs)
231            .map_err(|source| self.error(source, ErrorKind::Read))
232    }
233}
234
235impl From<File> for fs::File {
236    fn from(file: File) -> Self {
237        file.into_file()
238    }
239}
240
241impl Seek for File {
242    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
243        self.file
244            .seek(pos)
245            .map_err(|source| self.error(source, ErrorKind::Seek))
246    }
247}
248
249impl Seek for &File {
250    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
251        (&self.file)
252            .seek(pos)
253            .map_err(|source| self.error(source, ErrorKind::Seek))
254    }
255}
256
257impl Write for File {
258    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
259        self.file
260            .write(buf)
261            .map_err(|source| self.error(source, ErrorKind::Write))
262    }
263
264    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
265        self.file
266            .write_vectored(bufs)
267            .map_err(|source| self.error(source, ErrorKind::Write))
268    }
269
270    fn flush(&mut self) -> std::io::Result<()> {
271        self.file
272            .flush()
273            .map_err(|source| self.error(source, ErrorKind::Flush))
274    }
275}
276
277impl Write for &File {
278    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
279        (&self.file)
280            .write(buf)
281            .map_err(|source| self.error(source, ErrorKind::Write))
282    }
283
284    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
285        (&self.file)
286            .write_vectored(bufs)
287            .map_err(|source| self.error(source, ErrorKind::Write))
288    }
289
290    fn flush(&mut self) -> std::io::Result<()> {
291        (&self.file)
292            .flush()
293            .map_err(|source| self.error(source, ErrorKind::Flush))
294    }
295}
296
297#[cfg(unix)]
298mod unix {
299    use crate::os::unix::fs::FileExt;
300    use crate::ErrorKind;
301    use std::io;
302    use std::os::unix::fs::FileExt as _;
303    use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
304
305    impl AsRawFd for crate::File {
306        fn as_raw_fd(&self) -> RawFd {
307            self.file().as_raw_fd()
308        }
309    }
310
311    impl IntoRawFd for crate::File {
312        fn into_raw_fd(self) -> RawFd {
313            self.file.into_raw_fd()
314        }
315    }
316
317    impl FileExt for crate::File {
318        fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
319            self.file()
320                .read_at(buf, offset)
321                .map_err(|err| self.error(err, ErrorKind::ReadAt))
322        }
323        fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
324            self.file()
325                .write_at(buf, offset)
326                .map_err(|err| self.error(err, ErrorKind::WriteAt))
327        }
328    }
329
330    #[cfg(rustc_1_63)]
331    mod io_safety {
332        use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd};
333
334        impl AsFd for crate::File {
335            fn as_fd(&self) -> BorrowedFd<'_> {
336                self.file().as_fd()
337            }
338        }
339
340        impl From<crate::File> for OwnedFd {
341            fn from(file: crate::File) -> Self {
342                file.into_file().into()
343            }
344        }
345    }
346}
347
348#[cfg(windows)]
349mod windows {
350    use crate::os::windows::fs::FileExt;
351    use crate::ErrorKind;
352    use std::io;
353    use std::os::windows::{
354        fs::FileExt as _,
355        io::{AsRawHandle, IntoRawHandle, RawHandle},
356    };
357
358    impl FileExt for crate::File {
359        fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
360            self.file()
361                .seek_read(buf, offset)
362                .map_err(|err| self.error(err, ErrorKind::SeekRead))
363        }
364
365        fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
366            self.file()
367                .seek_write(buf, offset)
368                .map_err(|err| self.error(err, ErrorKind::SeekWrite))
369        }
370    }
371
372    impl AsRawHandle for crate::File {
373        fn as_raw_handle(&self) -> RawHandle {
374            self.file().as_raw_handle()
375        }
376    }
377
378    // can't be implemented, because the trait doesn't give us a Path
379    // impl std::os::windows::io::FromRawHandle for crate::File {
380    // }
381
382    impl IntoRawHandle for crate::File {
383        fn into_raw_handle(self) -> RawHandle {
384            self.file.into_raw_handle()
385        }
386    }
387
388    #[cfg(rustc_1_63)]
389    mod io_safety {
390        use std::os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle};
391
392        impl AsHandle for crate::File {
393            fn as_handle(&self) -> BorrowedHandle<'_> {
394                self.file().as_handle()
395            }
396        }
397
398        impl From<crate::File> for OwnedHandle {
399            fn from(file: crate::File) -> Self {
400                file.into_parts().0.into()
401            }
402        }
403    }
404}