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/// Locking methods added in Rust 1.89.
150#[cfg(rustc_1_89)]
151impl File {
152    /// Acquire an exclusive lock on the file. Blocks until the lock can be acquired.
153    ///
154    /// Wrapper for [`File::lock()`](https://doc.rust-lang.org/nightly/std/fs/struct.File.html#method.lock).
155    pub fn lock(&self) -> Result<(), io::Error> {
156        self.file
157            .lock()
158            .map_err(|source| self.error(source, ErrorKind::Lock))
159    }
160
161    /// Acquire a shared (non-exclusive) lock on the file. Blocks until the lock can be acquired.
162    ///
163    /// Wrapper for [`File::lock_shared()`](https://doc.rust-lang.org/nightly/std/fs/struct.File.html#method.lock_shared).
164    pub fn lock_shared(&self) -> Result<(), io::Error> {
165        self.file
166            .lock_shared()
167            .map_err(|source| self.error(source, ErrorKind::Lock))
168    }
169
170    /// Try to acquire an exclusive lock on the file.
171    ///
172    /// Wrapper for [`File::try_lock()`](https://doc.rust-lang.org/nightly/std/fs/struct.File.html#method.try_lock).
173    pub fn try_lock(&self) -> Result<(), fs::TryLockError> {
174        self.file.try_lock()
175    }
176
177    /// Try to acquire a shared (non-exclusive) lock on the file.
178    ///
179    /// Wrapper for [`File::try_lock_shared()`](https://doc.rust-lang.org/nightly/std/fs/struct.File.html#method.try_lock_shared).
180    pub fn try_lock_shared(&self) -> Result<(), fs::TryLockError> {
181        self.file.try_lock_shared()
182    }
183
184    /// Release all locks on the file.
185    ///
186    /// Wrapper for [`File::unlock()`](https://doc.rust-lang.org/nightly/std/fs/struct.File.html#method.unlock).
187    pub fn unlock(&self) -> Result<(), io::Error> {
188        self.file
189            .unlock()
190            .map_err(|source| self.error(source, ErrorKind::Unlock))
191    }
192}
193
194/// Methods added by fs-err that are not available on
195/// [`std::fs::File`][std::fs::File].
196///
197/// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
198impl File {
199    /// Creates a [`File`](struct.File.html) from a raw file and its path.
200    pub fn from_parts<P>(file: fs::File, path: P) -> Self
201    where
202        P: Into<PathBuf>,
203    {
204        File {
205            file,
206            path: path.into(),
207        }
208    }
209
210    /// Extract the raw file and its path from this [`File`](struct.File.html)
211    pub fn into_parts(self) -> (fs::File, PathBuf) {
212        (self.file, self.path)
213    }
214
215    /// Consumes this [`File`](struct.File.html) and returns the underlying
216    /// [`std::fs::File`][std::fs::File].
217    pub fn into_file(self) -> fs::File {
218        self.file
219    }
220
221    /// Consumes this [`File`](struct.File.html) and returns the underlying
222    /// path as a [`PathBuf`].
223    pub fn into_path(self) -> PathBuf {
224        self.path
225    }
226
227    /// Returns a reference to the underlying [`std::fs::File`][std::fs::File].
228    ///
229    /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
230    pub fn file(&self) -> &fs::File {
231        &self.file
232    }
233
234    /// Returns a mutable reference to the underlying [`std::fs::File`][std::fs::File].
235    ///
236    /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
237    pub fn file_mut(&mut self) -> &mut fs::File {
238        &mut self.file
239    }
240
241    /// Returns a reference to the path that this file was created with.
242    pub fn path(&self) -> &Path {
243        &self.path
244    }
245
246    /// Wrap the error in information specific to this `File` object.
247    fn error(&self, source: io::Error, kind: ErrorKind) -> io::Error {
248        Error::build(source, kind, &self.path)
249    }
250}
251
252impl Read for File {
253    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
254        self.file
255            .read(buf)
256            .map_err(|source| self.error(source, ErrorKind::Read))
257    }
258
259    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
260        self.file
261            .read_vectored(bufs)
262            .map_err(|source| self.error(source, ErrorKind::Read))
263    }
264}
265
266impl Read for &File {
267    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
268        (&self.file)
269            .read(buf)
270            .map_err(|source| self.error(source, ErrorKind::Read))
271    }
272
273    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
274        (&self.file)
275            .read_vectored(bufs)
276            .map_err(|source| self.error(source, ErrorKind::Read))
277    }
278}
279
280impl From<File> for fs::File {
281    fn from(file: File) -> Self {
282        file.into_file()
283    }
284}
285
286impl Seek for File {
287    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
288        self.file
289            .seek(pos)
290            .map_err(|source| self.error(source, ErrorKind::Seek))
291    }
292}
293
294impl Seek for &File {
295    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
296        (&self.file)
297            .seek(pos)
298            .map_err(|source| self.error(source, ErrorKind::Seek))
299    }
300}
301
302impl Write for File {
303    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
304        self.file
305            .write(buf)
306            .map_err(|source| self.error(source, ErrorKind::Write))
307    }
308
309    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
310        self.file
311            .write_vectored(bufs)
312            .map_err(|source| self.error(source, ErrorKind::Write))
313    }
314
315    fn flush(&mut self) -> std::io::Result<()> {
316        self.file
317            .flush()
318            .map_err(|source| self.error(source, ErrorKind::Flush))
319    }
320}
321
322impl Write for &File {
323    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
324        (&self.file)
325            .write(buf)
326            .map_err(|source| self.error(source, ErrorKind::Write))
327    }
328
329    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
330        (&self.file)
331            .write_vectored(bufs)
332            .map_err(|source| self.error(source, ErrorKind::Write))
333    }
334
335    fn flush(&mut self) -> std::io::Result<()> {
336        (&self.file)
337            .flush()
338            .map_err(|source| self.error(source, ErrorKind::Flush))
339    }
340}
341
342#[cfg(unix)]
343mod unix {
344    use crate::os::unix::fs::FileExt;
345    use crate::ErrorKind;
346    use std::io;
347    use std::os::unix::fs::FileExt as _;
348    use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
349
350    impl AsRawFd for crate::File {
351        fn as_raw_fd(&self) -> RawFd {
352            self.file().as_raw_fd()
353        }
354    }
355
356    impl IntoRawFd for crate::File {
357        fn into_raw_fd(self) -> RawFd {
358            self.file.into_raw_fd()
359        }
360    }
361
362    impl FileExt for crate::File {
363        fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
364            self.file()
365                .read_at(buf, offset)
366                .map_err(|err| self.error(err, ErrorKind::ReadAt))
367        }
368        fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
369            self.file()
370                .write_at(buf, offset)
371                .map_err(|err| self.error(err, ErrorKind::WriteAt))
372        }
373    }
374
375    #[cfg(rustc_1_63)]
376    mod io_safety {
377        use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd};
378
379        impl AsFd for crate::File {
380            fn as_fd(&self) -> BorrowedFd<'_> {
381                self.file().as_fd()
382            }
383        }
384
385        impl From<crate::File> for OwnedFd {
386            fn from(file: crate::File) -> Self {
387                file.into_file().into()
388            }
389        }
390    }
391}
392
393#[cfg(windows)]
394mod windows {
395    use crate::os::windows::fs::FileExt;
396    use crate::ErrorKind;
397    use std::io;
398    use std::os::windows::{
399        fs::FileExt as _,
400        io::{AsRawHandle, IntoRawHandle, RawHandle},
401    };
402
403    impl FileExt for crate::File {
404        fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
405            self.file()
406                .seek_read(buf, offset)
407                .map_err(|err| self.error(err, ErrorKind::SeekRead))
408        }
409
410        fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
411            self.file()
412                .seek_write(buf, offset)
413                .map_err(|err| self.error(err, ErrorKind::SeekWrite))
414        }
415    }
416
417    impl AsRawHandle for crate::File {
418        fn as_raw_handle(&self) -> RawHandle {
419            self.file().as_raw_handle()
420        }
421    }
422
423    // can't be implemented, because the trait doesn't give us a Path
424    // impl std::os::windows::io::FromRawHandle for crate::File {
425    // }
426
427    impl IntoRawHandle for crate::File {
428        fn into_raw_handle(self) -> RawHandle {
429            self.file.into_raw_handle()
430        }
431    }
432
433    #[cfg(rustc_1_63)]
434    mod io_safety {
435        use std::os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle};
436
437        impl AsHandle for crate::File {
438            fn as_handle(&self) -> BorrowedHandle<'_> {
439                self.file().as_handle()
440            }
441        }
442
443        impl From<crate::File> for OwnedHandle {
444            fn from(file: crate::File) -> Self {
445                file.into_parts().0.into()
446            }
447        }
448    }
449}