system_interface/fs/
fd_flags.rs

1use bitflags::bitflags;
2use io_lifetimes::{AsFilelike, FromFilelike};
3#[cfg(not(any(windows, target_os = "redox")))]
4use rustix::fs::{fcntl_getfl, fcntl_setfl, OFlags};
5#[cfg(not(windows))]
6use std::marker::PhantomData;
7use std::{fs, io};
8#[cfg(windows)]
9use {
10    cap_fs_ext::{OpenOptions, Reopen},
11    cap_std::fs::OpenOptionsExt,
12    io_lifetimes::AsHandle,
13    windows_sys::Win32::Storage::FileSystem::FILE_FLAG_WRITE_THROUGH,
14    winx::file::{AccessMode, FileModeInformation},
15};
16
17/// An opaque representation of the state needed to perform a `set_fd_flags`
18/// operation.
19#[cfg(windows)]
20pub struct SetFdFlags<T> {
21    reopened: T,
22}
23
24/// An opaque representation of the state needed to perform a `set_fd_flags`
25/// operation.
26#[cfg(not(windows))]
27pub struct SetFdFlags<T> {
28    flags: OFlags,
29    _phantom: PhantomData<T>,
30}
31
32/// Extension trait that can indicate various I/O flags.
33pub trait GetSetFdFlags {
34    /// Query the "status" flags for the `self` file descriptor.
35    fn get_fd_flags(&self) -> io::Result<FdFlags>
36    where
37        Self: AsFilelike;
38
39    /// Create a new `SetFdFlags` value for use with `set_fd_flags`.
40    ///
41    /// Some platforms lack the ability to dynamically set the flags and
42    /// implement `set_fd_flags` by closing and re-opening the resource and
43    /// splitting it into two steps like this simplifies the lifetimes.
44    fn new_set_fd_flags(&self, flags: FdFlags) -> io::Result<SetFdFlags<Self>>
45    where
46        Self: AsFilelike + FromFilelike + Sized;
47
48    /// Set the "status" flags for the `self` file descriptor.
49    ///
50    /// This requires a `SetFdFlags` obtained from `new_set_fd_flags`.
51    fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<Self>) -> io::Result<()>
52    where
53        Self: AsFilelike + Sized;
54}
55
56bitflags! {
57    /// Flag definitions for use with `SetFlags::set_flags`.
58    pub struct FdFlags: u32 {
59        /// Writes always write to the end of the file.
60        const APPEND = 0x01;
61
62        /// Write I/O operations on the file descriptor shall complete as
63        /// defined by synchronized I/O *data* integrity completion.
64        const DSYNC = 0x02;
65
66        /// I/O operations return `io::ErrorKind::WouldBlock`.
67        const NONBLOCK = 0x04;
68
69        /// Read I/O operations on the file descriptor shall complete at the
70        /// same level of integrity as specified by the `DSYNC` and `SYNC` flags.
71        const RSYNC = 0x08;
72
73        /// Write I/O operations on the file descriptor shall complete as
74        /// defined by synchronized I/O *file* integrity completion.
75        const SYNC = 0x10;
76    }
77}
78
79#[cfg(not(windows))]
80impl<T> GetSetFdFlags for T {
81    fn get_fd_flags(&self) -> io::Result<FdFlags>
82    where
83        Self: AsFilelike,
84    {
85        let mut fd_flags = FdFlags::empty();
86        let flags = fcntl_getfl(self)?;
87
88        fd_flags.set(FdFlags::APPEND, flags.contains(OFlags::APPEND));
89        #[cfg(not(target_os = "freebsd"))]
90        fd_flags.set(FdFlags::DSYNC, flags.contains(OFlags::DSYNC));
91        fd_flags.set(FdFlags::NONBLOCK, flags.contains(OFlags::NONBLOCK));
92        #[cfg(any(
93            target_os = "ios",
94            target_os = "macos",
95            target_os = "freebsd",
96            target_os = "fuchsia"
97        ))]
98        {
99            fd_flags.set(FdFlags::SYNC, flags.contains(OFlags::SYNC));
100        }
101        #[cfg(not(any(
102            target_os = "ios",
103            target_os = "macos",
104            target_os = "freebsd",
105            target_os = "fuchsia"
106        )))]
107        {
108            fd_flags.set(FdFlags::RSYNC, flags.contains(OFlags::RSYNC));
109            fd_flags.set(FdFlags::SYNC, flags.contains(OFlags::SYNC));
110        }
111
112        Ok(fd_flags)
113    }
114
115    fn new_set_fd_flags(&self, fd_flags: FdFlags) -> io::Result<SetFdFlags<Self>>
116    where
117        Self: AsFilelike,
118    {
119        let mut flags = OFlags::empty();
120        flags.set(OFlags::APPEND, fd_flags.contains(FdFlags::APPEND));
121        flags.set(OFlags::NONBLOCK, fd_flags.contains(FdFlags::NONBLOCK));
122
123        // Linux, FreeBSD, and others silently ignore these flags in `F_SETFL`.
124        if fd_flags.intersects(FdFlags::DSYNC | FdFlags::SYNC | FdFlags::RSYNC) {
125            return Err(io::Error::new(
126                io::ErrorKind::Other,
127                "setting fd_flags SYNC, DSYNC, and RSYNC is not supported",
128            ));
129        }
130
131        Ok(SetFdFlags {
132            flags,
133            _phantom: PhantomData,
134        })
135    }
136
137    fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<Self>) -> io::Result<()>
138    where
139        Self: AsFilelike + Sized,
140    {
141        Ok(fcntl_setfl(
142            &*self.as_filelike_view::<fs::File>(),
143            set_fd_flags.flags,
144        )?)
145    }
146}
147
148#[cfg(windows)]
149impl<T> GetSetFdFlags for T {
150    fn get_fd_flags(&self) -> io::Result<FdFlags>
151    where
152        Self: AsFilelike,
153    {
154        let mut fd_flags = FdFlags::empty();
155        let handle = self.as_filelike();
156        let access_mode = winx::file::query_access_information(handle)?;
157        let mode = winx::file::query_mode_information(handle)?;
158
159        // `FILE_APPEND_DATA` with `FILE_WRITE_DATA` means append-mode.
160        if access_mode.contains(AccessMode::FILE_APPEND_DATA)
161            && !access_mode.contains(AccessMode::FILE_WRITE_DATA)
162        {
163            fd_flags |= FdFlags::APPEND;
164        }
165
166        if mode.contains(FileModeInformation::FILE_WRITE_THROUGH) {
167            // Only report `DSYNC`. This is technically the only one of the
168            // `O_?SYNC` flags Windows supports.
169            fd_flags |= FdFlags::DSYNC;
170        }
171
172        Ok(fd_flags)
173    }
174
175    fn new_set_fd_flags(&self, fd_flags: FdFlags) -> io::Result<SetFdFlags<Self>>
176    where
177        Self: AsFilelike + FromFilelike,
178    {
179        let mut flags = 0;
180
181        if fd_flags.contains(FdFlags::SYNC) || fd_flags.contains(FdFlags::DSYNC) {
182            flags |= FILE_FLAG_WRITE_THROUGH;
183        }
184
185        let file = self.as_filelike_view::<fs::File>();
186        let access_mode = winx::file::query_access_information(file.as_handle())?;
187        let new_access_mode = file_access_mode_from_fd_flags(
188            fd_flags,
189            access_mode.contains(AccessMode::FILE_READ_DATA),
190            access_mode.contains(AccessMode::FILE_WRITE_DATA)
191                | access_mode.contains(AccessMode::FILE_APPEND_DATA),
192        );
193        let mut options = OpenOptions::new();
194        options.access_mode(new_access_mode.bits());
195        options.custom_flags(flags);
196        let reopened = Self::from_into_filelike(file.reopen(&options)?);
197        Ok(SetFdFlags { reopened })
198    }
199
200    fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<Self>) -> io::Result<()>
201    where
202        Self: AsFilelike,
203    {
204        *self = set_fd_flags.reopened;
205        Ok(())
206    }
207}
208
209#[cfg(windows)]
210fn file_access_mode_from_fd_flags(fd_flags: FdFlags, read: bool, write: bool) -> AccessMode {
211    let mut access_mode = AccessMode::READ_CONTROL;
212
213    // We always need `FILE_WRITE_ATTRIBUTES` so that we can set attributes
214    // such as filetimes, etc.
215    access_mode.insert(AccessMode::FILE_WRITE_ATTRIBUTES);
216
217    // Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly
218    // support append-only mode. The file-specific flags `FILE_GENERIC_READ`
219    // and `FILE_GENERIC_WRITE` are used here instead. These flags have the
220    // same semantic meaning for file objects, but allow removal of specific
221    // permissions (see below).
222    if read {
223        access_mode.insert(AccessMode::FILE_GENERIC_READ);
224    }
225    if write {
226        access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
227    }
228
229    // For append, grant the handle FILE_APPEND_DATA access but *not*
230    // `FILE_WRITE_DATA`. This makes the handle "append only". Changes to the
231    // file pointer will be ignored (like POSIX's `O_APPEND` behavior).
232    if fd_flags.contains(FdFlags::APPEND) {
233        access_mode.insert(AccessMode::FILE_APPEND_DATA);
234        access_mode.remove(AccessMode::FILE_WRITE_DATA);
235    }
236
237    access_mode
238}