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::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;