Skip to main content

win_durable_ioring/windows_backend/opened_file/
reopen.rs

1// Copyright (c) 2026 Mike Grier
2//! A reopen, through `ReOpenFile` (DI-15.4, DI-D-71): a new handle to what a handle already names, in
3//! a buffering mode, with an access and a sharing this crate states anew.
4//!
5//! The original is any handle, an [`OpenedFile`]'s or one opened elsewhere (`DI-D-68`): the new
6//! handle's mode is this crate's choice and is recorded by its type, so the original's provenance is
7//! not what the result claims. What a reopen cannot convert, such as a handle opened with sharing that
8//! refuses the reopen's access, is the kernel's refusal; the residue that matters is `DI-10.6`.
9//!
10//! The options are a type of their own, [`ReopenOptions`]: the page lists no disposition and says the
11//! flags cannot include attributes. The checks made on the reopened handle are the path open's, by
12//! the same function (DI-14.5), and here they can be reached: the original may be on a share, which
13//! the page lists as supported, or may be a device.
14
15use std::io;
16use std::marker::PhantomData;
17
18use win_shared_os_owned_handle::SharedHandle;
19use windows_namespace_request_sys::reopen::ReOpenFile;
20use windows_sys::Win32::Storage::FileSystem::{
21    FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT,
22    FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS,
23    FILE_FLAG_SEQUENTIAL_SCAN,
24};
25
26use durable_ioring::{DataAccess, NoData, ReadWrite};
27
28use super::{Buffering, Cached, OpenedBy, OpenedFile, data_access, data_rights, refine_sharing_by};
29use crate::windows_backend::file_rights::FileRights;
30use crate::windows_backend::share_mode::ShareMode;
31
32#[cfg(test)]
33mod tests;
34
35/// What to reopen with: the data access, the other rights, what other opens may do meanwhile, and the
36/// flags `ReOpenFile` takes that an open by path also offers. A reopen is shared for reading, writing
37/// and deletion until [`share`](Self::share) says otherwise, as [`FileOptions`](super::FileOptions)
38/// shares an open.
39///
40/// There is no disposition, since a reopen reaches only what is open, and no attributes, which the
41/// page says cannot be given. `FILE_FLAG_BACKUP_SEMANTICS` and `FILE_FLAG_WRITE_THROUGH`, which the
42/// page lists, are not in v1, as for an open by path; the named-pipe security flags it lists for a
43/// pipe's client are not offered either, since what this reopens is a file.
44///
45/// The share mode is judged against every handle still open to the file, **the original among them**:
46/// a reopen that does not share what the original asked for, or asks for what the original does not
47/// share, is refused, as the page says.
48///
49/// ```
50/// use win_durable_ioring::windows_backend::{ReadOnly, ReopenOptions};
51///
52/// let options: ReopenOptions<ReadOnly> = ReopenOptions::new().read();
53/// ```
54#[derive(Clone, Copy, Debug)]
55#[must_use]
56pub struct ReopenOptions<A: DataAccess = NoData> {
57    access: PhantomData<A>,
58    rights: FileRights,
59    share: ShareMode,
60    open_reparse_point: bool,
61    delete_on_close: bool,
62    open_no_recall: bool,
63    posix_semantics: bool,
64    overlapped: bool,
65    random_access: bool,
66    sequential_scan: bool,
67}
68
69impl Default for ReopenOptions<NoData> {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl ReopenOptions<NoData> {
76    /// Nothing requested: no data access and no other right, shared for everything.
77    pub fn new() -> Self {
78        Self {
79            access: PhantomData,
80            rights: FileRights::NONE,
81            share: ShareMode::ALL,
82            open_reparse_point: false,
83            delete_on_close: false,
84            open_no_recall: false,
85            posix_semantics: false,
86            overlapped: false,
87            random_access: false,
88            sequential_scan: false,
89        }
90    }
91}
92
93impl<A: DataAccess> ReopenOptions<A> {
94    /// Ask for the right to read the data, as [`FileOptions::read`](super::FileOptions::read) does.
95    pub fn read(self) -> ReopenOptions<A::WithRead> {
96        self.with_access()
97    }
98
99    /// Ask for the right to write the data, as [`FileOptions::write`](super::FileOptions::write) does.
100    pub fn write(self) -> ReopenOptions<A::WithWrite> {
101        self.with_access()
102    }
103
104    /// The rights besides the data access, as [`FileOptions::rights`](super::FileOptions::rights)
105    /// takes them. Calling this again replaces them.
106    pub fn rights(mut self, rights: FileRights) -> Self {
107        self.rights = rights;
108        self
109    }
110
111    /// What other opens may request while this handle is open (`dwShareMode`), as
112    /// [`FileOptions::share`](super::FileOptions::share) takes it. Calling this again replaces it.
113    pub fn share(mut self, share: ShareMode) -> Self {
114        self.share = share;
115        self
116    }
117
118    /// Reopen a reparse point itself (`FILE_FLAG_OPEN_REPARSE_POINT`). The page says the flag is
119    /// ignored for a file that is not a reparse point.
120    pub fn open_reparse_point(mut self, open_reparse_point: bool) -> Self {
121        self.open_reparse_point = open_reparse_point;
122        self
123    }
124
125    /// Delete the file when its last handle is closed (`FILE_FLAG_DELETE_ON_CLOSE`), as
126    /// [`FileOptions::delete_on_close`](super::FileOptions::delete_on_close) describes. The page says
127    /// the file goes when all of its handles are closed, the original's and its duplicates' too.
128    pub fn delete_on_close(mut self, delete_on_close: bool) -> Self {
129        self.delete_on_close = delete_on_close;
130        self
131    }
132
133    /// Ask that the data not be brought back from remote storage (`FILE_FLAG_OPEN_NO_RECALL`), as
134    /// [`FileOptions::open_no_recall`](super::FileOptions::open_no_recall) does.
135    pub fn open_no_recall(mut self, open_no_recall: bool) -> Self {
136        self.open_no_recall = open_no_recall;
137        self
138    }
139
140    /// Access the file by POSIX rules (`FILE_FLAG_POSIX_SEMANTICS`), as
141    /// [`FileOptions::posix_semantics`](super::FileOptions::posix_semantics) describes.
142    pub fn posix_semantics(mut self, posix_semantics: bool) -> Self {
143        self.posix_semantics = posix_semantics;
144        self
145    }
146
147    /// Reopen for overlapped I/O (`FILE_FLAG_OVERLAPPED`), as
148    /// [`FileOptions::overlapped`](super::FileOptions::overlapped) describes.
149    pub fn overlapped(mut self, overlapped: bool) -> Self {
150        self.overlapped = overlapped;
151        self
152    }
153
154    /// The random-access hint (`FILE_FLAG_RANDOM_ACCESS`), as
155    /// [`FileOptions::random_access`](super::FileOptions::random_access) describes.
156    pub fn random_access(mut self, random_access: bool) -> Self {
157        self.random_access = random_access;
158        self
159    }
160
161    /// The sequential-scan hint (`FILE_FLAG_SEQUENTIAL_SCAN`), as
162    /// [`FileOptions::sequential_scan`](super::FileOptions::sequential_scan) describes.
163    pub fn sequential_scan(mut self, sequential_scan: bool) -> Self {
164        self.sequential_scan = sequential_scan;
165        self
166    }
167
168    fn with_access<A2: DataAccess>(self) -> ReopenOptions<A2> {
169        ReopenOptions {
170            access: PhantomData,
171            rights: self.rights,
172            share: self.share,
173            open_reparse_point: self.open_reparse_point,
174            delete_on_close: self.delete_on_close,
175            open_no_recall: self.open_no_recall,
176            posix_semantics: self.posix_semantics,
177            overlapped: self.overlapped,
178            random_access: self.random_access,
179            sequential_scan: self.sequential_scan,
180        }
181    }
182
183    /// `dwDesiredAccess`: the data rights `A` names and the other rights.
184    fn access(self) -> u32 {
185        data_rights::<A>() | self.rights.bits()
186    }
187
188    /// `dwFlagsAndAttributes`: `mode`, the buffering mode's flags, and the flags the options name.
189    fn flags(self, mode: u32) -> u32 {
190        let mut flags = mode;
191        if self.open_reparse_point {
192            flags |= FILE_FLAG_OPEN_REPARSE_POINT;
193        }
194        if self.delete_on_close {
195            flags |= FILE_FLAG_DELETE_ON_CLOSE;
196        }
197        if self.open_no_recall {
198            flags |= FILE_FLAG_OPEN_NO_RECALL;
199        }
200        if self.posix_semantics {
201            flags |= FILE_FLAG_POSIX_SEMANTICS;
202        }
203        if self.overlapped {
204            flags |= FILE_FLAG_OVERLAPPED;
205        }
206        if self.random_access {
207            flags |= FILE_FLAG_RANDOM_ACCESS;
208        }
209        if self.sequential_scan {
210            flags |= FILE_FLAG_SEQUENTIAL_SCAN;
211        }
212        flags
213    }
214}
215
216/// The request to reopen a handle, built by [`OpenedFile::request_reopen`] and made by
217/// [`perform`](Self::perform). It holds the original, shared, so the handle it was built from can be
218/// closed first and the file stays reachable; it is `Send`, so it can be performed on a thread that
219/// can afford to block; and it can be performed more than once, each time making a new handle.
220#[derive(Clone, Debug)]
221#[must_use = "an unperformed request reopens nothing"]
222pub struct ReopenRequest<B: Buffering = Cached, A: DataAccess = ReadWrite> {
223    request: ReOpenFile,
224    rights: FileRights,
225    share: ShareMode,
226    mode: PhantomData<fn() -> (B, A)>,
227}
228
229impl<B: Buffering, A: DataAccess> ReopenRequest<B, A> {
230    /// The handle being reopened, shared with its other holders.
231    #[must_use]
232    pub fn original(&self) -> &SharedHandle {
233        self.request.original()
234    }
235
236    /// Reopen, on the calling thread. **This blocks for as long as `ReOpenFile` does.**
237    ///
238    /// # Errors
239    ///
240    /// - [`SharingViolation`](super::SharingViolation) (as an [`io::ErrorKind::ResourceBusy`] error),
241    ///   naming the file by [`OpenedBy::Reopen`], when the reopen's access or sharing conflicts with
242    ///   a handle still open to the file, the original among them.
243    /// - The path open's checks, made on the reopened handle by the same code:
244    ///   [`RemoteDevice`](super::RemoteDevice) when `B` is unbuffered and the original is on a share,
245    ///   and [`UnknownLocation`](super::UnknownLocation) when `B` is unbuffered and the reopened
246    ///   handle has no final path, as a device's does not.
247    /// - Otherwise whatever `ReOpenFile` reports, unaltered. The page says the original must have been
248    ///   created by `CreateFile`, and lists no codes.
249    pub fn perform(&self) -> io::Result<OpenedFile<B, A>> {
250        let handle = self.request.perform().map_err(|error| {
251            refine_sharing_by(
252                error.to_io_error(),
253                || OpenedBy::Reopen,
254                data_access::<A>(),
255                self.rights,
256                self.share,
257            )
258        })?;
259        OpenedFile::admit(SharedHandle::from(handle))
260    }
261}
262
263impl<B: Buffering, A: DataAccess> OpenedFile<B, A> {
264    /// Reopen what `original` names, in buffering mode `B` and as `options` say:
265    /// [`request_reopen`](Self::request_reopen) and then [`perform`](ReopenRequest::perform), one after
266    /// the other on this thread.
267    ///
268    /// The original is any handle: an `OpenedFile`'s, passed as `file.handle().clone()`, or one opened
269    /// elsewhere. The result is a new file of its own type, with the alignment of `B` and not the
270    /// original's: an unbuffered file starts at [`Unbuffered::DEFAULT_SECTOR_SIZE`](super::Unbuffered)
271    /// again, whatever sector size the original was given.
272    ///
273    /// ```
274    /// use win_durable_ioring::windows_backend::{
275    ///     Cached, FileOptions, OpenedFile, ReadOnly, ReopenOptions, Unbuffered,
276    /// };
277    /// use win_durable_ioring::Wtf16String;
278    ///
279    /// let path = std::env::temp_dir().join(format!("reopen-doc-{}.tmp", std::process::id()));
280    /// std::fs::write(&path, [0u8; 4096])?;
281    /// let name = Wtf16String::from_os_str(path.as_os_str());
282    /// let writer = OpenedFile::<Cached, _>::open(name, FileOptions::new().read().write())?;
283    ///
284    /// // A read-only, unbuffered handle beside the writer, for a scrubber.
285    /// let scrubber = OpenedFile::<Unbuffered, ReadOnly>::reopen(
286    ///     writer.handle().clone(),
287    ///     ReopenOptions::new().read(),
288    /// )?;
289    /// assert_eq!(scrubber.file_id()?, writer.file_id()?);
290    /// # drop((writer, scrubber));
291    /// # std::fs::remove_file(&path)?;
292    /// # Ok::<(), std::io::Error>(())
293    /// ```
294    ///
295    /// # Errors
296    ///
297    /// Those of [`perform`](ReopenRequest::perform).
298    pub fn reopen(
299        original: impl Into<SharedHandle>,
300        options: ReopenOptions<A>,
301    ) -> io::Result<Self> {
302        Self::request_reopen(original, options).perform()
303    }
304
305    /// The request to reopen what `original` names, built here and made wherever the caller chooses
306    /// with [`perform`](ReopenRequest::perform). Nothing is opened and nothing blocks. The original is
307    /// held, shared, and not duplicated.
308    pub fn request_reopen(
309        original: impl Into<SharedHandle>,
310        options: ReopenOptions<A>,
311    ) -> ReopenRequest<B, A> {
312        let request = ReOpenFile::new(original)
313            .with_desired_access(options.access())
314            .with_share_mode(options.share.bits())
315            .with_flags_and_attributes(options.flags(B::FLAGS));
316        ReopenRequest {
317            request,
318            rights: options.rights,
319            share: options.share,
320            mode: PhantomData,
321        }
322    }
323}