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}