windows_namespace_request_sys/close.rs
1// Copyright (c) Mike Grier.
2
3//! The close entries.
4//!
5//! Entry 4 of the audited catalogue, and the one whose membership surprises
6//! people.
7//!
8//! # Why closing is a catalogue entry at all
9//!
10//! `CloseHandle` looks like bookkeeping, but it is a blocking namespace call.
11//! It waits for outstanding I/O on the handle to complete, and on a dead
12//! network path or an ejected removable device it can block hard -- which is
13//! the whole reason this facility exists. A consumer that carefully moved its
14//! opens onto a worker and then closed on its own thread would have moved the
15//! wrong half.
16//!
17//! # A handle carries its close routine
18//!
19//! The audit found that a close entry **cannot assume its routine**:
20//! `FindCloseChangeNotification` closes an
21//! [`crate::watch::ChangeNotification`] and `CloseHandle` is wrong for it,
22//! silently. So the routine travels with the handle rather than being chosen at
23//! the call site, which is the same shape
24//! [windows-threadpool-sys](https://docs.rs/windows-threadpool-sys) already
25//! needed for wait targets.
26//!
27//! # A request is consumed by performing it
28//!
29//! [`CloseRequest::perform`] takes `self`, so a handle cannot be closed twice
30//! through this type. An unperformed request still closes its handle when
31//! dropped, because the alternative is a leak: a request that quietly did
32//! nothing would be worse than one that closes late.
33
34use std::ffi::c_void;
35use std::fmt;
36use std::mem::ManuallyDrop;
37use std::os::windows::io::{AsRawHandle, OwnedHandle};
38
39use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
40use windows_sys::Win32::Storage::FileSystem::FindCloseChangeNotification;
41use windows_sys::core::BOOL;
42
43use crate::outcome::{Outcome, perform_bool};
44use crate::watch::ChangeNotification;
45
46/// A Win32 routine that closes a handle.
47///
48/// This is the shape Win32 close routines already have, so one can be passed
49/// directly with no shim: `CloseHandle` and `FindCloseChangeNotification` both
50/// match it.
51pub type CloseFn = unsafe extern "system" fn(HANDLE) -> BOOL;
52
53/// An owned, marshalable request to close one handle.
54///
55/// The request owns the handle it will close, so the handle cannot be closed by
56/// anyone else in the meantime, and cannot outlive the request unclosed.
57///
58/// # Example
59///
60/// ```
61/// use std::fs;
62///
63/// use windows_namespace_request_sys::close::CloseRequest;
64///
65/// let path = std::env::temp_dir().join(format!("wnrs-close-{}.tmp", std::process::id()));
66/// fs::write(&path, b"example")?;
67/// let file = fs::File::open(&path)?;
68///
69/// // The close is a value now, so it can be performed wherever blocking is
70/// // acceptable rather than wherever the handle happens to be dropped.
71/// let request = CloseRequest::for_handle(file.into());
72/// request.perform()?;
73/// # fs::remove_file(&path)?;
74/// # Ok::<(), Box<dyn std::error::Error>>(())
75/// ```
76///
77/// # Example: the routine travels with the handle
78///
79/// A change notification is closed with `FindCloseChangeNotification`, and
80/// `CloseHandle` is silently wrong for it. A caller never has to know that,
81/// because the constructor pairs them:
82///
83/// ```
84/// use std::fs;
85///
86/// use windows_namespace_request_sys::close::CloseRequest;
87/// use windows_namespace_request_sys::prepare;
88/// use windows_namespace_request_sys::watch::{NotifyFilter, WatchDirectory};
89/// use wtf_string::Wtf16String;
90///
91/// let directory = std::env::temp_dir().join(format!("wnrs-cr-{}", std::process::id()));
92/// let _ = fs::remove_dir_all(&directory);
93/// fs::create_dir_all(&directory)?;
94/// let text = directory.to_str().expect("a temporary path is valid UTF-8");
95///
96/// let notification = WatchDirectory::new(prepare(&Wtf16String::from(text))?)
97/// .with_filter(NotifyFilter::FILE_NAME)
98/// .perform()?;
99///
100/// let request = CloseRequest::for_change_notification(notification);
101/// assert!(format!("{request:?}").contains("FindCloseChangeNotification"));
102/// request.perform()?;
103/// # let _ = fs::remove_dir_all(&directory);
104/// # Ok::<(), Box<dyn std::error::Error>>(())
105/// ```
106///
107/// # Example: performing consumes the request
108///
109/// This is what makes closing twice through this type impossible -- the second
110/// call does not compile:
111///
112/// ```compile_fail
113/// use std::fs;
114///
115/// use windows_namespace_request_sys::close::CloseRequest;
116///
117/// let path = std::env::temp_dir().join("wnrs-doc-double-close.tmp");
118/// fs::write(&path, b"x").unwrap();
119/// let request = CloseRequest::for_handle(fs::File::open(&path).unwrap().into());
120///
121/// request.perform().unwrap();
122/// request.perform().unwrap(); // error: use of moved value
123/// ```
124#[must_use = "dropping the request closes the handle immediately, on this thread"]
125pub struct CloseRequest {
126 /// Live until either `perform` or `Drop` closes it, exactly once.
127 handle: HANDLE,
128 close: CloseFn,
129}
130
131impl CloseRequest {
132 /// A request to close an ordinary handle with `CloseHandle`.
133 pub fn for_handle(handle: OwnedHandle) -> Self {
134 // The handle must not be closed by OwnedHandle's own drop, because this
135 // request now owns it.
136 let handle = ManuallyDrop::new(handle);
137
138 Self {
139 handle: handle.as_raw_handle().cast::<c_void>(),
140 close: CloseHandle,
141 }
142 }
143
144 /// A request to close a change notification with
145 /// `FindCloseChangeNotification`.
146 ///
147 /// Provided by name so a caller never has to know which routine is right:
148 /// this is precisely the pairing the audit found a close entry cannot
149 /// assume.
150 pub fn for_change_notification(notification: ChangeNotification) -> Self {
151 let raw = notification.as_raw();
152 // As above: this request takes over the close.
153 let _ = ManuallyDrop::new(notification);
154
155 Self {
156 handle: raw,
157 close: FindCloseChangeNotification,
158 }
159 }
160
161 /// A request to close `handle` with a caller-supplied routine.
162 ///
163 /// The escape hatch for a handle whose close routine this crate does not
164 /// know about, so an unanticipated variant needs no change here.
165 ///
166 /// # Safety
167 ///
168 /// `handle` must be a live handle that the caller owns and gives up, and
169 /// `close` must be the correct routine for it.
170 pub unsafe fn from_raw(handle: HANDLE, close: CloseFn) -> Self {
171 Self { handle, close }
172 }
173
174 /// The handle this request will close.
175 #[must_use]
176 pub fn handle(&self) -> HANDLE {
177 self.handle
178 }
179
180 /// Performs the close on the calling thread.
181 ///
182 /// Consumes the request, so a handle cannot be closed twice through this
183 /// type.
184 ///
185 /// # Errors
186 ///
187 /// Returns the raw Win32 code, unaltered. The handle is closed either way:
188 /// a failed close is not a close that can be retried.
189 pub fn perform(self) -> Outcome<()> {
190 // Drop must not run: it would close the handle a second time.
191 let request = ManuallyDrop::new(self);
192
193 // SAFETY: the handle is live and owned by this request, which is being
194 // consumed, and `close` is its correct routine by construction.
195 perform_bool(|| unsafe { (request.close)(request.handle) })
196 }
197}
198
199impl Drop for CloseRequest {
200 fn drop(&mut self) {
201 // SAFETY: the handle is live and owned here, and this runs exactly once
202 // -- `perform` consumes the request through a `ManuallyDrop`. The
203 // result is ignored because a destructor has nowhere to report it.
204 unsafe {
205 (self.close)(self.handle);
206 }
207 }
208}
209
210impl fmt::Debug for CloseRequest {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 f.debug_struct("CloseRequest")
213 .field("handle", &self.handle)
214 .field("close", &self.close_routine_name())
215 .finish()
216 }
217}
218
219impl CloseRequest {
220 /// Names the close routine, for diagnostics.
221 ///
222 /// Comparing function pointers is not generally meaningful, but these two
223 /// are the ones this crate installs, so recognising them is worth the
224 /// caveat that an unrecognised routine simply reports as custom.
225 fn close_routine_name(&self) -> &'static str {
226 let close = self.close as *const ();
227
228 if close == CloseHandle as *const () {
229 "CloseHandle"
230 } else if close == FindCloseChangeNotification as *const () {
231 "FindCloseChangeNotification"
232 } else {
233 "custom"
234 }
235 }
236}
237
238// SAFETY: the request owns its handle exclusively and has no interior
239// mutability. A Windows handle is process-wide rather than thread-affine, so a
240// close performed on another thread closes the same object; the raw pointer is
241// what blocks the automatic derivation.
242unsafe impl Send for CloseRequest {}
243// SAFETY: as above. Every method that could close the handle takes `self`.
244unsafe impl Sync for CloseRequest {}
245
246impl crate::request::ConsumingRequest for CloseRequest {
247 type Error = crate::Win32Error;
248 type Output = ();
249
250 fn perform(self) -> Outcome<()> {
251 Self::perform(self)
252 }
253}
254
255#[cfg(test)]
256mod tests;