Skip to main content

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;