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::watch::{NotifyFilter, WatchDirectory};
88/// use wtf_string::Wtf16String;
89///
90/// let directory = std::env::temp_dir().join(format!("wnrs-cr-{}", std::process::id()));
91/// let _ = fs::remove_dir_all(&directory);
92/// fs::create_dir_all(&directory)?;
93/// let text = directory.to_str().expect("a temporary path is valid UTF-8");
94///
95/// let notification = WatchDirectory::new(Wtf16String::from(text))?
96/// .with_filter(NotifyFilter::FILE_NAME)
97/// .perform()?;
98///
99/// let request = CloseRequest::for_change_notification(notification);
100/// assert!(format!("{request:?}").contains("FindCloseChangeNotification"));
101/// request.perform()?;
102/// # let _ = fs::remove_dir_all(&directory);
103/// # Ok::<(), Box<dyn std::error::Error>>(())
104/// ```
105///
106/// # Example: performing consumes the request
107///
108/// This is what makes closing twice through this type impossible -- the second
109/// call does not compile:
110///
111/// ```compile_fail,E0382
112/// use std::fs;
113///
114/// use windows_namespace_request_sys::close::CloseRequest;
115///
116/// let path = std::env::temp_dir().join("wnrs-doc-double-close.tmp");
117/// fs::write(&path, b"x").unwrap();
118/// let request = CloseRequest::for_handle(fs::File::open(&path).unwrap().into());
119///
120/// request.perform().unwrap();
121/// request.perform().unwrap(); // error: use of moved value
122/// ```
123#[must_use = "dropping the request closes the handle immediately, on this thread"]
124pub struct CloseRequest {
125 /// Live until either `perform` or `Drop` closes it, exactly once.
126 handle: HANDLE,
127 close: CloseFn,
128}
129
130impl CloseRequest {
131 /// A request to close an ordinary handle with `CloseHandle`.
132 pub fn for_handle(handle: OwnedHandle) -> Self {
133 // The handle must not be closed by OwnedHandle's own drop, because this
134 // request now owns it.
135 let handle = ManuallyDrop::new(handle);
136
137 Self {
138 handle: handle.as_raw_handle().cast::<c_void>(),
139 close: CloseHandle,
140 }
141 }
142
143 /// A request to close a change notification with
144 /// `FindCloseChangeNotification`.
145 ///
146 /// Provided by name so a caller never has to know which routine is right:
147 /// this is precisely the pairing the audit found a close entry cannot
148 /// assume.
149 pub fn for_change_notification(notification: ChangeNotification) -> Self {
150 let raw = notification.as_raw();
151 // As above: this request takes over the close.
152 let _ = ManuallyDrop::new(notification);
153
154 Self {
155 handle: raw,
156 close: FindCloseChangeNotification,
157 }
158 }
159
160 /// A request to close `handle` with a caller-supplied routine.
161 ///
162 /// The escape hatch for a handle whose close routine this crate does not
163 /// know about, so an unanticipated variant needs no change here.
164 ///
165 /// # Safety
166 ///
167 /// `handle` must be a live handle that the caller owns and gives up, and
168 /// `close` must be the correct routine for it.
169 pub unsafe fn from_raw(handle: HANDLE, close: CloseFn) -> Self {
170 Self { handle, close }
171 }
172
173 /// The handle this request will close.
174 #[must_use]
175 pub fn handle(&self) -> HANDLE {
176 self.handle
177 }
178
179 /// Performs the close on the calling thread.
180 ///
181 /// Consumes the request, so a handle cannot be closed twice through this
182 /// type.
183 ///
184 /// # Errors
185 ///
186 /// Returns the raw Win32 code, unaltered. The handle is closed either way:
187 /// a failed close is not a close that can be retried.
188 pub fn perform(self) -> Outcome<()> {
189 // Drop must not run: it would close the handle a second time.
190 let request = ManuallyDrop::new(self);
191
192 // SAFETY: the handle is live and owned by this request, which is being
193 // consumed, and `close` is its correct routine by construction.
194 perform_bool(|| unsafe { (request.close)(request.handle) })
195 }
196}
197
198impl Drop for CloseRequest {
199 fn drop(&mut self) {
200 // SAFETY: the handle is live and owned here, and this runs exactly once
201 // -- `perform` consumes the request through a `ManuallyDrop`. The
202 // result is ignored because a destructor has nowhere to report it.
203 unsafe {
204 (self.close)(self.handle);
205 }
206 }
207}
208
209impl fmt::Debug for CloseRequest {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 f.debug_struct("CloseRequest")
212 .field("handle", &self.handle)
213 .field("close", &self.close_routine_name())
214 .finish()
215 }
216}
217
218impl CloseRequest {
219 /// Names the close routine, for diagnostics.
220 ///
221 /// Comparing function pointers is not generally meaningful, but these two
222 /// are the ones this crate installs, so recognising them is worth the
223 /// caveat that an unrecognised routine simply reports as custom.
224 fn close_routine_name(&self) -> &'static str {
225 let close = self.close as *const ();
226
227 if close == CloseHandle as *const () {
228 "CloseHandle"
229 } else if close == FindCloseChangeNotification as *const () {
230 "FindCloseChangeNotification"
231 } else {
232 "custom"
233 }
234 }
235}
236
237// SAFETY: the request owns its handle exclusively and has no interior
238// mutability. A Windows handle is process-wide rather than thread-affine, so a
239// close performed on another thread closes the same object; the raw pointer is
240// what blocks the automatic derivation.
241unsafe impl Send for CloseRequest {}
242// SAFETY: as above. Every method that could close the handle takes `self`.
243unsafe impl Sync for CloseRequest {}
244
245impl crate::request::ConsumingRequest for CloseRequest {
246 type Error = crate::Win32Error;
247 type Output = ();
248
249 fn perform(self) -> Outcome<()> {
250 Self::perform(self)
251 }
252}
253
254#[cfg(test)]
255mod tests;