system_interface/fs/
file_io_ext.rs

1//! The `FileIoExt` trait, and related utilities and impls.
2
3use crate::io::IoExt;
4use io_lifetimes::AsFilelike;
5#[cfg(not(any(
6    windows,
7    target_os = "ios",
8    target_os = "macos",
9    target_os = "netbsd",
10    target_os = "openbsd",
11    target_os = "redox",
12)))]
13use rustix::fs::fadvise;
14#[cfg(any(target_os = "ios", target_os = "macos"))]
15use rustix::fs::fcntl_rdadvise;
16#[cfg(not(any(
17    windows,
18    target_os = "netbsd",
19    target_os = "redox",
20    target_os = "openbsd"
21)))]
22use rustix::fs::{fallocate, FallocateFlags};
23#[cfg(not(any(windows, target_os = "ios", target_os = "macos", target_os = "redox")))]
24use rustix::io::{preadv, pwritev};
25use std::io::{self, IoSlice, IoSliceMut, Seek, SeekFrom};
26use std::slice;
27#[cfg(windows)]
28use {cap_fs_ext::Reopen, std::fs, std::os::windows::fs::FileExt};
29#[cfg(not(windows))]
30use {rustix::fs::tell, rustix::fs::FileExt};
31
32/// Advice to pass to `FileIoExt::advise`.
33#[cfg(not(any(
34    windows,
35    target_os = "ios",
36    target_os = "macos",
37    target_os = "netbsd",
38    target_os = "openbsd",
39    target_os = "redox"
40)))]
41#[derive(Debug, Eq, PartialEq, Hash)]
42#[repr(i32)]
43pub enum Advice {
44    /// No advice; default heuristics apply.
45    Normal = rustix::fs::Advice::Normal as i32,
46    /// Data will be accessed sequentially at ascending offsets.
47    Sequential = rustix::fs::Advice::Sequential as i32,
48    /// Data will be accessed with an irregular access pattern.
49    Random = rustix::fs::Advice::Random as i32,
50    /// Data will be accessed soon.
51    WillNeed = rustix::fs::Advice::WillNeed as i32,
52    /// Data will not be accessed soon.
53    DontNeed = rustix::fs::Advice::DontNeed as i32,
54    /// Data will be accessed exactly once.
55    NoReuse = rustix::fs::Advice::NoReuse as i32,
56}
57
58/// Advice to pass to `FileIoExt::advise`.
59#[cfg(any(
60    windows,
61    target_os = "ios",
62    target_os = "macos",
63    target_os = "netbsd",
64    target_os = "openbsd",
65    target_os = "redox"
66))]
67#[derive(Debug, Eq, PartialEq, Hash)]
68pub enum Advice {
69    /// No advice; default heuristics apply.
70    Normal,
71    /// Data will be accessed sequentially at ascending offsets.
72    Sequential,
73    /// Data will be accessed with an irregular access pattern.
74    Random,
75    /// Data will be accessed soon.
76    WillNeed,
77    /// Data will not be accessed soon.
78    DontNeed,
79    /// Data will be accessed exactly once.
80    NoReuse,
81}
82
83/// Extension trait for `std::fs::File` and `cap_std::fs::File`.
84pub trait FileIoExt: IoExt {
85    /// Announce the expected access pattern of the data at the given offset.
86    fn advise(&self, offset: u64, len: u64, advice: Advice) -> io::Result<()>;
87
88    /// Allocate space in the file, increasing the file size as needed, and
89    /// ensuring that there are no holes under the given range.
90    fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
91
92    /// Reads a number of bytes starting from a given offset.
93    ///
94    /// This is similar to [`std::os::unix::fs::FileExt::read_at`], except it
95    /// takes `self` by immutable reference since the entire side effect is
96    /// I/O, and it's supported on non-Unix platforms including Windows.
97    ///
98    /// [`std::os::unix::fs::FileExt::read_at`]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.read_at
99    fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
100
101    /// Reads the exact number of byte required to fill buf from the given
102    /// offset.
103    ///
104    /// This is similar to [`std::os::unix::fs::FileExt::read_exact_at`],
105    /// except it takes `self` by immutable reference since the entire side
106    /// effect is I/O, and it's supported on non-Unix platforms including
107    /// Windows.
108    ///
109    /// [`std::os::unix::fs::FileExt::read_exact_at`]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.read_exact_at
110    fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()>;
111
112    /// Is to `read_vectored` what `read_at` is to `read`.
113    fn read_vectored_at(&self, bufs: &mut [IoSliceMut], offset: u64) -> io::Result<usize> {
114        // By default, just read into the first non-empty slice.
115        let buf = bufs
116            .iter_mut()
117            .find(|b| !b.is_empty())
118            .map_or(&mut [][..], |b| &mut **b);
119        self.read_at(buf, offset)
120    }
121
122    /// Is to `read_exact_vectored` what `read_exact_at` is to `read_exact`.
123    fn read_exact_vectored_at(
124        &self,
125        mut bufs: &mut [IoSliceMut],
126        mut offset: u64,
127    ) -> io::Result<()> {
128        bufs = skip_leading_empties(bufs);
129        while !bufs.is_empty() {
130            match self.read_vectored_at(bufs, offset) {
131                Ok(0) => {
132                    return Err(io::Error::new(
133                        io::ErrorKind::UnexpectedEof,
134                        "failed to fill whole buffer",
135                    ))
136                }
137                Ok(nread) => {
138                    offset = offset
139                        .checked_add(nread.try_into().unwrap())
140                        .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "offset overflow"))?;
141                    bufs = advance_mut(bufs, nread);
142                }
143                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => (),
144                Err(e) => return Err(e),
145            }
146            bufs = skip_leading_empties(bufs);
147        }
148        Ok(())
149    }
150
151    /// Determines if this `FileIoExt` implementation has an efficient
152    /// `read_vectored_at` implementation.
153    #[inline]
154    fn is_read_vectored_at(&self) -> bool {
155        false
156    }
157
158    /// Read all bytes, starting at `offset`, until EOF in this source, placing
159    /// them into `buf`.
160    fn read_to_end_at(&self, buf: &mut Vec<u8>, offset: u64) -> io::Result<usize>;
161
162    /// Read all bytes, starting at `offset`, until EOF in this source,
163    /// appending them to `buf`.
164    fn read_to_string_at(&self, buf: &mut String, offset: u64) -> io::Result<usize>;
165
166    /// Writes a number of bytes starting from a given offset.
167    ///
168    /// This is similar to [`std::os::unix::fs::FileExt::write_at`], except it
169    /// takes `self` by immutable reference since the entire side effect is
170    /// I/O, and it's supported on non-Unix platforms including Windows.
171    ///
172    /// A write past the end of the file extends the file with zero bytes until
173    /// the point where the write starts.
174    ///
175    /// Contrary to POSIX, on many popular platforms including Linux and
176    /// FreeBSD, if the file is opened in append mode, this ignores the offset
177    /// appends the data to the end of the file.
178    ///
179    /// [`std::os::unix::fs::FileExt::write_at`]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.write_at
180    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
181
182    /// Attempts to write an entire buffer starting from a given offset.
183    ///
184    /// This is similar to [`std::os::unix::fs::FileExt::write_all_at`], except
185    /// it takes `self` by immutable reference since the entire side effect is
186    /// I/O, and it's supported on non-Unix platforms including Windows.
187    ///
188    /// A write past the end of the file extends the file with zero bytes until
189    /// the point where the write starts.
190    ///
191    /// Contrary to POSIX, on many popular platforms including Linux and
192    /// FreeBSD, if the file is opened in append mode, this ignores the offset
193    /// appends the data to the end of the file.
194    ///
195    /// [`std::os::unix::fs::FileExt::write_all_at`]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.write_all_at
196    fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
197        while !buf.is_empty() {
198            match self.write_at(buf, offset) {
199                Ok(nwritten) => {
200                    buf = &buf[nwritten..];
201                    offset += nwritten as u64;
202                }
203                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => (),
204                Err(e) => return Err(e),
205            }
206        }
207        Ok(())
208    }
209
210    /// Is to `write_vectored` what `write_at` is to `write`.
211    fn write_vectored_at(&self, bufs: &[IoSlice], offset: u64) -> io::Result<usize> {
212        // By default, just write the first non-empty slice.
213        let buf = bufs
214            .iter()
215            .find(|b| !b.is_empty())
216            .map_or(&[][..], |b| &**b);
217        self.write_at(buf, offset)
218    }
219
220    /// Is to `write_all_vectored` what `write_all_at` is to `write_all`.
221    fn write_all_vectored_at(&self, mut bufs: &mut [IoSlice], mut offset: u64) -> io::Result<()> {
222        while !bufs.is_empty() {
223            match self.write_vectored_at(bufs, offset) {
224                Ok(nwritten) => {
225                    offset = offset
226                        .checked_add(nwritten.try_into().unwrap())
227                        .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "offset overflow"))?;
228                    bufs = advance(bufs, nwritten);
229                }
230                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => (),
231                Err(e) => return Err(e),
232            }
233        }
234        Ok(())
235    }
236
237    /// Determines if this `FileIoExt` implementation has an efficient
238    /// `write_vectored_at` implementation.
239    #[inline]
240    fn is_write_vectored_at(&self) -> bool {
241        false
242    }
243
244    /// Writes a number of bytes at the end of a file.
245    ///
246    /// This leaves the current position of the file unmodified.
247    ///
248    /// This operation is not guaranteed to be atomic with respect to other
249    /// users of the same open file description.
250    ///
251    /// This operation is less efficient on some platforms than opening the
252    /// file in append mode and doing regular writes.
253    fn append(&self, buf: &[u8]) -> io::Result<usize>;
254
255    /// Attempts to write an entire buffer at the end of a file.
256    ///
257    /// This leaves the current position of the file unmodified.
258    ///
259    /// This operation is not guaranteed to be atomic with respect to other
260    /// users of the same open file description.
261    ///
262    /// This operation is less efficient on some platforms than opening the
263    /// file in append mode and doing regular writes.
264    fn append_all(&self, mut buf: &[u8]) -> io::Result<()> {
265        while !buf.is_empty() {
266            match self.append(buf) {
267                Ok(nwritten) => {
268                    buf = &buf[nwritten..];
269                }
270                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => (),
271                Err(e) => return Err(e),
272            }
273        }
274        Ok(())
275    }
276
277    /// Is to `append` what `write_vectored` is to `write`.
278    fn append_vectored(&self, bufs: &[IoSlice]) -> io::Result<usize> {
279        // By default, just append the first non-empty slice.
280        let buf = bufs
281            .iter()
282            .find(|b| !b.is_empty())
283            .map_or(&[][..], |b| &**b);
284        self.append(buf)
285    }
286
287    /// Is to `append_all` what `write_all_vectored` is to `write_all`.
288    fn append_all_vectored(&self, mut bufs: &mut [IoSlice]) -> io::Result<()> {
289        while !bufs.is_empty() {
290            match self.append_vectored(bufs) {
291                Ok(nwritten) => {
292                    bufs = advance(bufs, nwritten);
293                }
294                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => (),
295                Err(e) => return Err(e),
296            }
297        }
298        Ok(())
299    }
300
301    /// Determines if this `FileIoExt` implementation has an efficient
302    /// `append_vectored` implementation.
303    #[inline]
304    fn is_append_vectored(&self) -> bool {
305        false
306    }
307
308    /// Seek to an offset, in bytes, in a stream.
309    ///
310    /// This is similar to [`std::io::Seek::seek`], except it takes `self` by
311    /// immutable reference since the entire side effect is I/O.
312    ///
313    /// [`std::io::Seek::seek`]: https://doc.rust-lang.org/std/io/trait.Seek.html#tymethod.seek
314    fn seek(&self, pos: SeekFrom) -> io::Result<u64>;
315
316    /// Returns the current seek position from the start of the stream.
317    ///
318    /// This is similar to [`std::io::Seek::stream_position`], except it's
319    /// available on Rust stable.
320    ///
321    /// This may eventually be implemented by [rust-lang/rust#62726].
322    ///
323    /// [`std::io::Seek::stream_position`]: https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position
324    /// [rust-lang/rust#62726]: https://github.com/rust-lang/rust/issues/59359.
325    fn stream_position(&self) -> io::Result<u64>;
326}
327
328/// Skip any leading elements in `bufs` which are empty buffers.
329fn skip_leading_empties<'a, 'b>(mut bufs: &'b mut [IoSliceMut<'a>]) -> &'b mut [IoSliceMut<'a>] {
330    while !bufs.is_empty() {
331        if !bufs[0].is_empty() {
332            break;
333        }
334        bufs = &mut bufs[1..];
335    }
336    bufs
337}
338
339/// This will be obviated by [rust-lang/rust#62726].
340///
341/// [rust-lang/rust#62726]: https://github.com/rust-lang/rust/issues/62726.
342fn advance<'a, 'b>(bufs: &'b mut [IoSlice<'a>], n: usize) -> &'b mut [IoSlice<'a>] {
343    // Number of buffers to remove.
344    let mut remove = 0;
345    // Total length of all the to be removed buffers.
346    let mut accumulated_len = 0;
347    for buf in bufs.iter() {
348        if accumulated_len + buf.len() > n {
349            break;
350        } else {
351            accumulated_len += buf.len();
352            remove += 1;
353        }
354    }
355
356    #[allow(clippy::indexing_slicing)]
357    let bufs = &mut bufs[remove..];
358    if let Some(first) = bufs.first_mut() {
359        let advance_by = n - accumulated_len;
360        let mut ptr = first.as_ptr();
361        let mut len = first.len();
362        unsafe {
363            ptr = ptr.add(advance_by);
364            len -= advance_by;
365            *first = IoSlice::<'a>::new(slice::from_raw_parts::<'a>(ptr, len));
366        }
367    }
368    bufs
369}
370
371/// This will be obviated by [rust-lang/rust#62726].
372///
373/// [rust-lang/rust#62726]: https://github.com/rust-lang/rust/issues/62726.
374fn advance_mut<'a, 'b>(bufs: &'b mut [IoSliceMut<'a>], n: usize) -> &'b mut [IoSliceMut<'a>] {
375    // Number of buffers to remove.
376    let mut remove = 0;
377    // Total length of all the to be removed buffers.
378    let mut accumulated_len = 0;
379    for buf in bufs.iter() {
380        if accumulated_len + buf.len() > n {
381            break;
382        } else {
383            accumulated_len += buf.len();
384            remove += 1;
385        }
386    }
387
388    #[allow(clippy::indexing_slicing)]
389    let bufs = &mut bufs[remove..];
390    if let Some(first) = bufs.first_mut() {
391        let advance_by = n - accumulated_len;
392        let mut ptr = first.as_mut_ptr();
393        let mut len = first.len();
394        unsafe {
395            ptr = ptr.add(advance_by);
396            len -= advance_by;
397            *first = IoSliceMut::<'a>::new(slice::from_raw_parts_mut::<'a>(ptr, len));
398        }
399    }
400    bufs
401}
402
403/// Implement `FileIoExt` for any type which implements `AsRawFd`.
404#[cfg(not(windows))]
405impl<T: AsFilelike + IoExt> FileIoExt for T {
406    #[cfg(not(any(
407        target_os = "ios",
408        target_os = "macos",
409        target_os = "netbsd",
410        target_os = "openbsd",
411        target_os = "redox"
412    )))]
413    #[inline]
414    fn advise(&self, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
415        let advice = match advice {
416            Advice::WillNeed => rustix::fs::Advice::WillNeed,
417            Advice::Normal => rustix::fs::Advice::Normal,
418            Advice::Sequential => rustix::fs::Advice::Sequential,
419            Advice::NoReuse => rustix::fs::Advice::NoReuse,
420            Advice::Random => rustix::fs::Advice::Random,
421            Advice::DontNeed => rustix::fs::Advice::DontNeed,
422        };
423        Ok(fadvise(self, offset, len, advice)?)
424    }
425
426    #[cfg(any(target_os = "ios", target_os = "macos"))]
427    #[inline]
428    fn advise(&self, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
429        // Darwin lacks `posix_fadvise`, but does have an `fcntl_rdadvise`
430        // feature which roughly corresponds to `WillNeed`. This is not yet
431        // tuned.
432        match advice {
433            Advice::WillNeed => (),
434            Advice::Normal
435            | Advice::Sequential
436            | Advice::NoReuse
437            | Advice::Random
438            | Advice::DontNeed => return Ok(()),
439        }
440
441        Ok(fcntl_rdadvise(self, offset, len)?)
442    }
443
444    #[cfg(any(target_os = "netbsd", target_os = "redox", target_os = "openbsd"))]
445    #[inline]
446    fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> io::Result<()> {
447        // Netbsd lacks `posix_fadvise` and doesn't have an obvious replacement,
448        // so just ignore the advice.
449        Ok(())
450    }
451
452    #[cfg(not(any(target_os = "netbsd", target_os = "redox", target_os = "openbsd")))]
453    #[inline]
454    fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
455        Ok(fallocate(self, FallocateFlags::empty(), offset, len)?)
456    }
457
458    #[cfg(target_os = "netbsd")]
459    fn allocate(&self, _offset: u64, _len: u64) -> io::Result<()> {
460        todo!("NetBSD 7.0 supports posix_fallocate; add bindings for it")
461    }
462
463    #[cfg(target_os = "openbsd")]
464    fn allocate(&self, _offset: u64, _len: u64) -> io::Result<()> {
465        todo!("OpenBSD does not support posix_fallocate; figure out what to do")
466    }
467
468    #[cfg(target_os = "redox")]
469    fn allocate(&self, _offset: u64, _len: u64) -> io::Result<()> {
470        todo!("figure out what to do on redox for posix_fallocate")
471    }
472
473    #[inline]
474    fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
475        FileExt::read_at(&*self.as_filelike_view::<std::fs::File>(), buf, offset)
476    }
477
478    #[inline]
479    fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> {
480        FileExt::read_exact_at(&*self.as_filelike_view::<std::fs::File>(), buf, offset)
481    }
482
483    #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
484    #[inline]
485    fn read_vectored_at(&self, bufs: &mut [IoSliceMut], offset: u64) -> io::Result<usize> {
486        Ok(preadv(self, bufs, offset)?)
487    }
488
489    #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
490    #[inline]
491    fn is_read_vectored_at(&self) -> bool {
492        true
493    }
494
495    #[inline]
496    fn read_to_end_at(&self, buf: &mut Vec<u8>, offset: u64) -> io::Result<usize> {
497        read_to_end_at(&self.as_filelike_view::<std::fs::File>(), buf, offset)
498    }
499
500    #[inline]
501    fn read_to_string_at(&self, buf: &mut String, offset: u64) -> io::Result<usize> {
502        read_to_string_at(&self.as_filelike_view::<std::fs::File>(), buf, offset)
503    }
504
505    #[inline]
506    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
507        FileExt::write_at(&*self.as_filelike_view::<std::fs::File>(), buf, offset)
508    }
509
510    #[inline]
511    fn write_all_at(&self, buf: &[u8], offset: u64) -> io::Result<()> {
512        FileExt::write_all_at(&*self.as_filelike_view::<std::fs::File>(), buf, offset)
513    }
514
515    #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
516    #[inline]
517    fn write_vectored_at(&self, bufs: &[IoSlice], offset: u64) -> io::Result<usize> {
518        Ok(pwritev(self, bufs, offset)?)
519    }
520
521    #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
522    #[inline]
523    fn is_write_vectored_at(&self) -> bool {
524        true
525    }
526
527    fn append(&self, buf: &[u8]) -> io::Result<usize> {
528        use rustix::fs::{fcntl_getfl, fcntl_setfl, seek, OFlags, SeekFrom};
529        use rustix::io::write;
530
531        // On Linux, use `pwritev2`.
532        #[cfg(any(target_os = "android", target_os = "linux"))]
533        {
534            use rustix::io::{pwritev2, Errno, ReadWriteFlags};
535
536            let iovs = [IoSlice::new(buf)];
537            match pwritev2(self, &iovs, 0, ReadWriteFlags::APPEND) {
538                Err(Errno::NOSYS) | Err(Errno::NOTSUP) => {}
539                otherwise => return Ok(otherwise?),
540            }
541        }
542
543        // Otherwise use `F_SETFL` to switch the file description to append
544        // mode, do the write, and switch back. This is not atomic with
545        // respect to other users of the file description, but this is
546        // possibility is documented in the trait.
547        //
548        // Optimization idea: some users don't care about the current position,
549        // changing, so perhaps we could add a `append_relaxed` etc. API which
550        // doesn't preserve positions.
551        let old_flags = fcntl_getfl(self)?;
552        let old_pos = tell(self)?;
553        fcntl_setfl(self, old_flags | OFlags::APPEND)?;
554        let result = write(self, buf);
555        fcntl_setfl(self, old_flags).unwrap();
556        seek(self, SeekFrom::Start(old_pos)).unwrap();
557        Ok(result?)
558    }
559
560    fn append_vectored(&self, bufs: &[IoSlice]) -> io::Result<usize> {
561        use rustix::fs::{fcntl_getfl, fcntl_setfl, seek, OFlags, SeekFrom};
562        use rustix::io::writev;
563
564        // On Linux, use `pwritev2`.
565        #[cfg(any(target_os = "android", target_os = "linux"))]
566        {
567            use rustix::io::{pwritev2, Errno, ReadWriteFlags};
568
569            match pwritev2(self, bufs, 0, ReadWriteFlags::APPEND) {
570                Err(Errno::NOSYS) | Err(Errno::NOTSUP) => {}
571                otherwise => return Ok(otherwise?),
572            }
573        }
574
575        // Otherwise use `F_SETFL` to switch the file description to append
576        // mode, do the write, and switch back. This is not atomic with
577        // respect to other users of the file description, but this is
578        // possibility is documented in the trait.
579        let old_flags = fcntl_getfl(self)?;
580        let old_pos = tell(self)?;
581        fcntl_setfl(self, old_flags | OFlags::APPEND)?;
582        let result = writev(self, bufs);
583        fcntl_setfl(self, old_flags).unwrap();
584        seek(self, SeekFrom::Start(old_pos)).unwrap();
585        Ok(result?)
586    }
587
588    #[inline]
589    fn is_append_vectored(&self) -> bool {
590        true
591    }
592
593    #[inline]
594    fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
595        Seek::seek(&mut &*self.as_filelike_view::<std::fs::File>(), pos)
596    }
597
598    #[inline]
599    fn stream_position(&self) -> io::Result<u64> {
600        // This may eventually be obsoleted by [rust-lang/rust#59359].
601        // [rust-lang/rust#59359]: https://github.com/rust-lang/rust/issues/59359.
602        Ok(tell(self)?)
603    }
604}
605
606#[cfg(windows)]
607impl FileIoExt for std::fs::File {
608    #[inline]
609    fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> io::Result<()> {
610        // TODO: Do something with the advice.
611        Ok(())
612    }
613
614    #[inline]
615    fn allocate(&self, _offset: u64, _len: u64) -> io::Result<()> {
616        // We can't faithfully support allocate on Windows without exposing race
617        // conditions. Instead, refuse:
618        Err(io::Error::new(
619            io::ErrorKind::PermissionDenied,
620            "file allocate is not supported on Windows",
621        ))
622    }
623
624    #[inline]
625    fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
626        // Windows' `seek_read` modifies the current position in the file, so
627        // re-open the file to leave the original open file unmodified.
628        //
629        // Optimization idea: some users don't care about the current position,
630        // changing, so perhaps we could add a `read_at_relaxed` etc. API which
631        // doesn't do the `reopen`.
632        let reopened = reopen(self)?;
633        reopened.seek_read(buf, offset)
634    }
635
636    #[inline]
637    fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> {
638        // Similar to `read_at`, re-open the file so that we can do a seek and
639        // leave the original file unmodified.
640        let reopened = loop {
641            match reopen(self) {
642                Ok(file) => break file,
643                Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
644                Err(err) => return Err(err),
645            }
646        };
647        loop {
648            match reopened.seek(SeekFrom::Start(offset)) {
649                Ok(_) => break,
650                Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
651                Err(err) => return Err(err),
652            }
653        }
654        reopened.read_exact(buf)
655    }
656
657    #[inline]
658    fn read_vectored_at(&self, bufs: &mut [IoSliceMut], offset: u64) -> io::Result<usize> {
659        // Similar to `read_at`, re-open the file so that we can do a seek and
660        // leave the original file unmodified.
661        let reopened = reopen(self)?;
662        reopened.seek(SeekFrom::Start(offset))?;
663        reopened.read_vectored(bufs)
664    }
665
666    #[inline]
667    fn read_exact_vectored_at(&self, bufs: &mut [IoSliceMut], offset: u64) -> io::Result<()> {
668        // Similar to `read_vectored_at`, re-open the file so that we can do a seek and
669        // leave the original file unmodified.
670        let reopened = loop {
671            match reopen(self) {
672                Ok(file) => break file,
673                Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
674                Err(err) => return Err(err),
675            }
676        };
677        loop {
678            match reopened.seek(SeekFrom::Start(offset)) {
679                Ok(_) => break,
680                Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
681                Err(err) => return Err(err),
682            }
683        }
684        reopened.read_exact_vectored(bufs)
685    }
686
687    #[inline]
688    fn is_read_vectored_at(&self) -> bool {
689        true
690    }
691
692    #[inline]
693    fn read_to_end_at(&self, buf: &mut Vec<u8>, offset: u64) -> io::Result<usize> {
694        read_to_end_at(&self.as_filelike_view::<std::fs::File>(), buf, offset)
695    }
696
697    #[inline]
698    fn read_to_string_at(&self, buf: &mut String, offset: u64) -> io::Result<usize> {
699        read_to_string_at(&self.as_filelike_view::<std::fs::File>(), buf, offset)
700    }
701
702    #[inline]
703    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
704        // Windows' `seek_write` modifies the current position in the file, so
705        // re-open the file to leave the original open file unmodified.
706        reopen_write(self)?.seek_write(buf, offset)
707    }
708
709    #[inline]
710    fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
711        // Similar to `read_exact_at`, re-open the file so that we can do a seek
712        // and leave the original file unmodified.
713        let reopened = reopen_write(self)?;
714        while buf.len() > 0 {
715            let n = reopened.seek_write(buf, offset)?;
716            offset += u64::try_from(n).unwrap();
717            buf = &buf[n..];
718        }
719        Ok(())
720    }
721
722    #[inline]
723    fn write_vectored_at(&self, bufs: &[IoSlice], offset: u64) -> io::Result<usize> {
724        // Windows doesn't have a vectored write for files, so pick the first
725        // non-empty slice and write that.
726        match bufs.iter().find(|p| p.len() > 0) {
727            Some(buf) => self.write_at(buf, offset),
728            None => Ok(0),
729        }
730    }
731
732    #[inline]
733    fn write_all_vectored_at(&self, bufs: &mut [IoSlice], mut offset: u64) -> io::Result<()> {
734        for buf in bufs {
735            self.write_all_at(buf, offset)?;
736            offset += u64::try_from(buf.len()).unwrap();
737        }
738        Ok(())
739    }
740
741    #[inline]
742    fn is_write_vectored_at(&self) -> bool {
743        true
744    }
745
746    fn append(&self, buf: &[u8]) -> io::Result<usize> {
747        // Re-open the file for appending.
748        let reopened = reopen_append(self)?;
749        reopened.write(buf)
750    }
751
752    fn append_vectored(&self, bufs: &[IoSlice]) -> io::Result<usize> {
753        // Re-open the file for appending.
754        let reopened = reopen_append(self)?;
755        reopened.write_vectored(bufs)
756    }
757
758    #[inline]
759    fn is_append_vectored(&self) -> bool {
760        true
761    }
762
763    #[inline]
764    fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
765        Seek::seek(&mut &*self.as_filelike_view::<std::fs::File>(), pos)
766    }
767
768    #[inline]
769    fn stream_position(&self) -> io::Result<u64> {
770        // This may eventually be obsoleted by [rust-lang/rust#59359].
771        // [rust-lang/rust#59359]: https://github.com/rust-lang/rust/issues/59359.
772        Seek::seek(
773            &mut &*self.as_filelike_view::<std::fs::File>(),
774            SeekFrom::Current(0),
775        )
776    }
777}
778
779#[cfg(windows)]
780#[cfg(feature = "cap_std_impls")]
781impl FileIoExt for cap_std::fs::File {
782    #[inline]
783    fn advise(&self, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
784        self.as_filelike_view::<std::fs::File>()
785            .advise(offset, len, advice)
786    }
787
788    #[inline]
789    fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
790        self.as_filelike_view::<std::fs::File>()
791            .allocate(offset, len)
792    }
793
794    #[inline]
795    fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
796        self.as_filelike_view::<std::fs::File>()
797            .read_at(buf, offset)
798    }
799
800    #[inline]
801    fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> {
802        self.as_filelike_view::<std::fs::File>()
803            .read_exact_at(buf, offset)
804    }
805
806    #[inline]
807    fn read_vectored_at(&self, bufs: &mut [IoSliceMut], offset: u64) -> io::Result<usize> {
808        self.as_filelike_view::<std::fs::File>()
809            .read_vectored_at(bufs, offset)
810    }
811
812    #[inline]
813    fn read_exact_vectored_at(&self, bufs: &mut [IoSliceMut], offset: u64) -> io::Result<()> {
814        self.as_filelike_view::<std::fs::File>()
815            .read_exact_vectored_at(bufs, offset)
816    }
817
818    #[inline]
819    fn is_read_vectored_at(&self) -> bool {
820        self.as_filelike_view::<std::fs::File>()
821            .is_read_vectored_at()
822    }
823
824    #[inline]
825    fn read_to_end_at(&self, buf: &mut Vec<u8>, offset: u64) -> io::Result<usize> {
826        self.as_filelike_view::<std::fs::File>()
827            .read_to_end_at(buf, offset)
828    }
829
830    #[inline]
831    fn read_to_string_at(&self, buf: &mut String, offset: u64) -> io::Result<usize> {
832        self.as_filelike_view::<std::fs::File>()
833            .read_to_string_at(buf, offset)
834    }
835
836    #[inline]
837    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
838        self.as_filelike_view::<std::fs::File>()
839            .write_at(buf, offset)
840    }
841
842    #[inline]
843    fn write_all_at(&self, buf: &[u8], offset: u64) -> io::Result<()> {
844        self.as_filelike_view::<std::fs::File>()
845            .write_all_at(buf, offset)
846    }
847
848    #[inline]
849    fn write_vectored_at(&self, bufs: &[IoSlice], offset: u64) -> io::Result<usize> {
850        self.as_filelike_view::<std::fs::File>()
851            .write_vectored_at(bufs, offset)
852    }
853
854    #[inline]
855    fn write_all_vectored_at(&self, bufs: &mut [IoSlice], offset: u64) -> io::Result<()> {
856        self.as_filelike_view::<std::fs::File>()
857            .write_all_vectored_at(bufs, offset)
858    }
859
860    #[inline]
861    fn is_write_vectored_at(&self) -> bool {
862        self.as_filelike_view::<std::fs::File>()
863            .is_write_vectored_at()
864    }
865
866    fn append(&self, buf: &[u8]) -> io::Result<usize> {
867        self.as_filelike_view::<std::fs::File>().append(buf)
868    }
869
870    fn append_vectored(&self, bufs: &[IoSlice]) -> io::Result<usize> {
871        self.as_filelike_view::<std::fs::File>()
872            .append_vectored(bufs)
873    }
874
875    #[inline]
876    fn is_append_vectored(&self) -> bool {
877        true
878    }
879
880    #[inline]
881    fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
882        self.as_filelike_view::<std::fs::File>().seek(pos)
883    }
884
885    #[inline]
886    fn stream_position(&self) -> io::Result<u64> {
887        self.as_filelike_view::<std::fs::File>().stream_position()
888    }
889}
890
891#[cfg(windows)]
892#[cfg(feature = "cap_std_impls_fs_utf8")]
893impl FileIoExt for cap_std::fs_utf8::File {
894    #[inline]
895    fn advise(&self, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
896        self.as_filelike_view::<std::fs::File>()
897            .advise(offset, len, advice)
898    }
899
900    #[inline]
901    fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
902        self.as_filelike_view::<std::fs::File>()
903            .allocate(offset, len)
904    }
905
906    #[inline]
907    fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
908        self.as_filelike_view::<std::fs::File>()
909            .read_at(buf, offset)
910    }
911
912    #[inline]
913    fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> {
914        self.as_filelike_view::<std::fs::File>()
915            .read_exact_at(buf, offset)
916    }
917
918    #[inline]
919    fn read_vectored_at(&self, bufs: &mut [IoSliceMut], offset: u64) -> io::Result<usize> {
920        self.as_filelike_view::<std::fs::File>()
921            .read_vectored_at(bufs, offset)
922    }
923
924    #[inline]
925    fn read_exact_vectored_at(&self, bufs: &mut [IoSliceMut], offset: u64) -> io::Result<()> {
926        self.as_filelike_view::<std::fs::File>()
927            .read_exact_vectored_at(bufs, offset)
928    }
929
930    #[inline]
931    fn is_read_vectored_at(&self) -> bool {
932        self.as_filelike_view::<std::fs::File>()
933            .is_read_vectored_at()
934    }
935
936    #[inline]
937    fn read_to_end_at(&self, buf: &mut Vec<u8>, offset: u64) -> io::Result<usize> {
938        self.as_filelike_view::<std::fs::File>()
939            .read_to_end_at(buf, offset)
940    }
941
942    #[inline]
943    fn read_to_string_at(&self, buf: &mut String, offset: u64) -> io::Result<usize> {
944        self.as_filelike_view::<std::fs::File>()
945            .read_to_string_at(buf, offset)
946    }
947
948    #[inline]
949    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
950        self.as_filelike_view::<std::fs::File>()
951            .write_at(buf, offset)
952    }
953
954    #[inline]
955    fn write_all_at(&self, buf: &[u8], offset: u64) -> io::Result<()> {
956        self.as_filelike_view::<std::fs::File>()
957            .write_all_at(buf, offset)
958    }
959
960    #[inline]
961    fn write_vectored_at(&self, bufs: &[IoSlice], offset: u64) -> io::Result<usize> {
962        self.as_filelike_view::<std::fs::File>()
963            .write_vectored_at(bufs, offset)
964    }
965
966    #[inline]
967    fn write_all_vectored_at(&self, bufs: &mut [IoSlice], offset: u64) -> io::Result<()> {
968        self.as_filelike_view::<std::fs::File>()
969            .write_all_vectored_at(bufs, offset)
970    }
971
972    #[inline]
973    fn is_write_vectored_at(&self) -> bool {
974        self.as_filelike_view::<std::fs::File>()
975            .is_write_vectored_at()
976    }
977
978    fn append(&self, buf: &[u8]) -> io::Result<usize> {
979        self.as_filelike_view::<std::fs::File>().append(buf)
980    }
981
982    fn append_vectored(&self, bufs: &[IoSlice]) -> io::Result<usize> {
983        self.as_filelike_view::<std::fs::File>()
984            .append_vectored(bufs)
985    }
986
987    #[inline]
988    fn is_append_vectored(&self) -> bool {
989        true
990    }
991
992    #[inline]
993    fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
994        self.as_filelike_view::<std::fs::File>().seek(pos)
995    }
996
997    #[inline]
998    fn stream_position(&self) -> io::Result<u64> {
999        self.as_filelike_view::<std::fs::File>().stream_position()
1000    }
1001}
1002
1003#[cfg(windows)]
1004#[inline]
1005fn reopen<Filelike: AsFilelike>(filelike: &Filelike) -> io::Result<fs::File> {
1006    let file = filelike.as_filelike_view::<std::fs::File>();
1007    unsafe { _reopen(&file) }
1008}
1009
1010#[cfg(windows)]
1011unsafe fn _reopen(file: &fs::File) -> io::Result<fs::File> {
1012    file.reopen(cap_fs_ext::OpenOptions::new().read(true))
1013}
1014
1015#[cfg(windows)]
1016#[inline]
1017fn reopen_write<Filelike: AsFilelike>(filelike: &Filelike) -> io::Result<fs::File> {
1018    let file = filelike.as_filelike_view::<std::fs::File>();
1019    unsafe { _reopen_write(&file) }
1020}
1021
1022#[cfg(windows)]
1023unsafe fn _reopen_write(file: &fs::File) -> io::Result<fs::File> {
1024    file.reopen(cap_fs_ext::OpenOptions::new().write(true))
1025}
1026
1027#[cfg(windows)]
1028#[inline]
1029fn reopen_append<Filelike: AsFilelike>(filelike: &Filelike) -> io::Result<fs::File> {
1030    let file = filelike.as_filelike_view::<std::fs::File>();
1031    unsafe { _reopen_append(&file) }
1032}
1033
1034#[cfg(windows)]
1035unsafe fn _reopen_append(file: &fs::File) -> io::Result<fs::File> {
1036    file.reopen(cap_fs_ext::OpenOptions::new().append(true))
1037}
1038
1039fn read_to_end_at(file: &std::fs::File, buf: &mut Vec<u8>, offset: u64) -> io::Result<usize> {
1040    let len = match file.metadata()?.len().checked_sub(offset) {
1041        None => return Ok(0),
1042        Some(len) => len,
1043    };
1044
1045    // This initializes the buffer with zeros which is theoretically
1046    // unnecessary, but current alternatives involve tricky `unsafe` code.
1047    buf.resize(
1048        (buf.len() as u64)
1049            .saturating_add(len)
1050            .try_into()
1051            .unwrap_or(usize::MAX),
1052        0_u8,
1053    );
1054    FileIoExt::read_exact_at(file, buf, offset)?;
1055    Ok(len as usize)
1056}
1057
1058fn read_to_string_at(file: &std::fs::File, buf: &mut String, offset: u64) -> io::Result<usize> {
1059    let len = match file.metadata()?.len().checked_sub(offset) {
1060        None => return Ok(0),
1061        Some(len) => len,
1062    };
1063
1064    // This temporary buffer is theoretically unnecessary, but eliminating it
1065    // currently involves a bunch of `unsafe`.
1066    let mut tmp = vec![0_u8; len.try_into().unwrap_or(usize::MAX)];
1067    FileIoExt::read_exact_at(file, &mut tmp, offset)?;
1068    let s = String::from_utf8(tmp).map_err(|_| {
1069        io::Error::new(
1070            io::ErrorKind::InvalidData,
1071            "stream did not contain valid UTF-8",
1072        )
1073    })?;
1074    buf.push_str(&s);
1075    Ok(len as usize)
1076}
1077
1078fn _file_io_ext_can_be_trait_object(_: &dyn FileIoExt) {}